capability

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package capability owns the assistant's capability-document schema and the composition that turns a project's documents into the model's system-prompt knowledge and provider tools.

This is the contract inversion made concrete: the assistant, not the control plane, owns the capability-document type. The on-the-wire JSON shape is still the services.miloapis.com/v1alpha1 AgentBinding projection (so existing fixtures parse unchanged), but the Go types here are assistant-native and carry no control-plane client. A "capability provider" publishes documents; how they reach the assistant is a [CapabilitySource] concern (fixture file today, an HTTP source later).

Index

Constants

View Source
const (
	// DefaultMCPConnectTimeout bounds a single MCP server connect.
	DefaultMCPConnectTimeout = 5 * time.Second
	// ToolNamespaceSeparator joins the server shortname and the provider tool
	// name into the model-facing tool name.
	ToolNamespaceSeparator = "__"
)

Tool composition defaults (Tier 2).

View Source
const (
	// AuthorizationHeader carries the caller's own bearer token.
	AuthorizationHeader = "Authorization"
	// ProjectHeader names the project the provider must read in. It comes from
	// the authorized request, never from a model-supplied tool argument.
	ProjectHeader = "X-Datum-Project"
)

Caller-identity propagation to provider MCP servers, so one can read the customer's own resources as the caller. See docs/architecture/identity-and-access.md.

A capability document is provider-controlled data, so naming an endpoint grants it nothing: identity travels only to hosts an OPERATOR sanctioned (ComposeOptions.IdentityForwardHosts), empty by default. Knowledge sources and skill bodies are excluded outright — plain GETs of URLs named by that same document, so a credential there would only widen the harvest surface.

View Source
const (
	// DefaultKnowledgeTimeout bounds each source fetch (connect + body read).
	DefaultKnowledgeTimeout = 3 * time.Second
	// DefaultKnowledgeMaxBytesPerSource caps how many bytes are read per source.
	DefaultKnowledgeMaxBytesPerSource = 32 * 1024
	// DefaultKnowledgeMaxSourcesPerService caps how many sources one service may inject.
	DefaultKnowledgeMaxSourcesPerService = 8
	// TruncationMarker is appended to a source body that hit the byte cap.
	TruncationMarker = "[knowledge truncated at size cap]"
)

Knowledge composition defaults (Tier 1).

View Source
const (
	// RememberMemoryToolName is the model-facing name of the fact-writing tool.
	RememberMemoryToolName = "memory_remember"
	// ForgetMemoryToolName is the model-facing name of the fact-deleting tool.
	ForgetMemoryToolName = "memory_forget"
)

Project memory: durable, project-scoped facts (goals, conventions, decisions, standing constraints) that persist across conversations and are visible to every user working on the same project — distinct from conversation history, which is per-(project, contextId) and windowed. See internal/memory for the storage layer.

View Source
const (
	// LoadSkillToolName is the model-facing name of the built-in skill loader.
	LoadSkillToolName = "load_skill"
	// DefaultSkillTimeout bounds one skill-body fetch.
	DefaultSkillTimeout = 5 * time.Second
	// DefaultSkillMaxBytes caps a skill body (a skill is a procedure, not a
	// document dump).
	DefaultSkillMaxBytes = 64 * 1024
)

Skills composition: providers publish reviewed procedures (see Skill); composition puts only their names + descriptions into the system prompt ("progressive disclosure") and exposes one built-in tool, load_skill, that fetches a skill's body on demand. The body is provider-authored INSTRUCTION content — the platform prompt scopes what a loaded skill may direct (only that provider's services, never overriding platform rules), and skills grant no privileges beyond the independently allow-listed tools.

View Source
const GapReportToolBaseName = "report_capability_gap"

GapReportToolBaseName is the un-namespaced model-facing tool name for capability-gap reporting. Each provider service that declares a ReportingProject gets its own namespaced instance — see GapReportToolName — so the model can only report a gap against a service actually present in the current conversation, never an arbitrary provider it names in free text.

View Source
const MaxInjectedCapabilityKeys = 40

MaxInjectedCapabilityKeys caps how many of a service's existing capability keys are shown to the model in the gap-report tool schema, which sits in EVERY turn's prompt for an entitled service. Truncating is safe because the list is most-hit first — a cap can only drop the keys least likely to be filed again.

Variables

This section is empty.

Functions

func GapReportToolName

func GapReportToolName(serviceRefName string) string

GapReportToolName renders the model-facing name for one provider's gap- report tool, "report_capability_gap__<serviceRef>", sanitized the same way provider MCP tools are namespaced (see NamespaceToolName).

func NamespaceToolName

func NamespaceToolName(serverName, toolName string) string

NamespaceToolName renders the model-facing name "<server>__<tool>", with both parts sanitized to the [a-zA-Z0-9_-] set model providers require.

func SanitizeName

func SanitizeName(v string) string

SanitizeName reduces a provider-supplied name to the character set tool and skill identifiers use. Exported so anything deriving an identifier from a document (the agent card's per-service skill IDs) applies the same rule as NamespaceToolName rather than passing an arbitrary provider string through.

Types

type Authority

type Authority struct {
	Reads                  []AuthorityRead `json:"reads,omitempty"`
	MaxTaskDurationSeconds *int            `json:"maxTaskDurationSeconds,omitempty"`
}

Authority describes the read scope and time budget granted to the agent.

type AuthorityRead

type AuthorityRead struct {
	GVK GVKRef `json:"gvk"`
}

AuthorityRead names a resource kind the agent is authorized to read.

type CallerIdentity

type CallerIdentity struct {
	// BearerToken is the raw token from the authenticated request. Empty means
	// there is no identity to forward (any non-HTTP entry point).
	BearerToken string
}

CallerIdentity is the calling user's own credential, threaded through composition so a sanctioned provider endpoint can act as that user. String and LogValue redact the token: a stray %v or slog attribute must never be able to write a live credential to a log.

func (CallerIdentity) LogValue

func (c CallerIdentity) LogValue() slog.Value

func (CallerIdentity) String

func (c CallerIdentity) String() string

type CapabilityDocument

type CapabilityDocument struct {
	APIVersion string         `json:"apiVersion,omitempty"`
	Kind       string         `json:"kind,omitempty"`
	Metadata   *Metadata      `json:"metadata,omitempty"`
	Spec       CapabilitySpec `json:"spec"`
	Status     *Status        `json:"status,omitempty"`
}

CapabilityDocument is one project-scoped entitlement: a provider service and the capabilities it grants the assistant. The JSON shape matches the CRD projection; unknown fields are ignored on parse so newer projections never break an older assistant.

func ParseDocuments

func ParseDocuments(raw []byte, onSkip func(index int, err error)) ([]CapabilityDocument, error)

ParseDocuments parses a capability-document fixture: either a bare JSON array of documents or a List object ({"items": [...]}). Entries that fail CapabilityDocument.Validate are skipped and reported to onSkip (if non-nil) rather than failing the whole file, so one malformed document never takes down a project's capabilities. A malformed root (bad JSON, or neither array nor list) is a hard error.

func ScopeDocuments

func ScopeDocuments(docs []CapabilityDocument, expectedProject string, logger *slog.Logger) []CapabilityDocument

ScopeDocuments is the defense-in-depth tenant-isolation seam. The capability Source is trusted to return only the calling project's documents; this guards against a Source bug (or a compromised fan-out) leaking another tenant's document by dropping any whose Metadata.Namespace names a different project. It fails closed only on a positive mismatch: a document with no namespace is kept, because the schema carries no other project handle to cross-check and the Source stays the scoping authority there. With no ExpectedProject the check is disabled and docs pass through unchanged (backward compatible).

func (*CapabilityDocument) Validate

func (d *CapabilityDocument) Validate() error

Validate reports whether the document satisfies the required-field constraints (the Go analogue of the zod schema). It returns a clear, path-qualified error on the first violation. Unknown fields are not an error; missing or empty required fields are.

type CapabilitySpec

type CapabilitySpec struct {
	ServiceRef           Ref        `json:"serviceRef"`
	ServiceName          string     `json:"serviceName"`
	ServiceAgentRef      Ref        `json:"serviceAgentRef"`
	ConfigurationVersion string     `json:"configurationVersion"`
	Knowledge            *Knowledge `json:"knowledge,omitempty"`
	Tools                *Tools     `json:"tools,omitempty"`
	Skills               []Skill    `json:"skills,omitempty"`
	Authority            *Authority `json:"authority,omitempty"`
	// ReportingProject is the Milo project where this service's own team
	// reviews capability-gap reports (see internal/gapreport) — resolved by
	// the service catalog from its own service registration, distinct from
	// Metadata.Namespace (the consumer project this document was entitled
	// to). Optional: when a document declares Tools but no
	// ReportingProject, capability-gap reporting is simply unavailable for
	// that service (no tool is registered) rather than an error.
	ReportingProject string `json:"reportingProject,omitempty"`
}

CapabilitySpec is the meat of a capability document: which provider service it entitles and the knowledge/tools/authority it projects.

type ComposeOptions

type ComposeOptions struct {
	// HTTPClient fetches knowledge sources. Nil uses http.DefaultClient.
	HTTPClient *http.Client
	// KnowledgeTimeout, KnowledgeMaxBytesPerSource, KnowledgeMaxSourcesPerService
	// override the Tier-1 defaults when > 0.
	KnowledgeTimeout              time.Duration
	KnowledgeMaxBytesPerSource    int
	KnowledgeMaxSourcesPerService int
	// MCPConnectTimeout overrides [DefaultMCPConnectTimeout] when > 0.
	MCPConnectTimeout time.Duration
	// SkillTimeout and SkillMaxBytes override the skill-body fetch defaults
	// when > 0 (see DefaultSkillTimeout / DefaultSkillMaxBytes).
	SkillTimeout  time.Duration
	SkillMaxBytes int

	// OnToolInvocation, if set, fires once at the start of every provider-tool
	// execution (wired to usage metering by the caller).
	OnToolInvocation func(ProviderToolInvocation)
	// Logger receives composition warnings. Nil discards them.
	Logger *slog.Logger
	// AllowPrivateNetworks disables the SSRF IP guard's private/loopback/
	// link-local block for the knowledge, skill, and MCP fetches. It is the
	// dev/cluster escape hatch: local overlays address services over loopback
	// and in-cluster (private) IPs, which the guard blocks by default. It MUST
	// stay false in production. Default false = guard on (safe).
	AllowPrivateNetworks bool
	// AllowedHosts and AllowedCIDRs, when either is non-empty, switch the SSRF
	// guard from its default "block private" IP-policy into an allow-list posture
	// for UNTRUSTED providers: a capability-document URL (knowledge source, skill
	// source, MCP endpoint) is permitted only if its host matches an AllowedHosts
	// entry or resolves into an AllowedCIDRs range. AllowedHosts entries match a
	// host exactly and as a domain suffix ("example.com" permits "example.com"
	// and "api.example.com"); AllowedCIDRs is the reviewed gateway range(s).
	//
	// The always-blocked set (link-local/IMDS, unspecified, multicast) still
	// holds in allow-list mode — an allow-listed host that resolves to metadata
	// is refused. When both are empty the guard keeps its IP-policy behavior
	// (backward compatible). The integrator populates these from config.
	AllowedHosts []string
	AllowedCIDRs []string
	// Caller is the calling user's own credential, forwarded (with
	// ExpectedProject) only to endpoints in IdentityForwardHosts so a provider
	// can read as the caller instead of holding standing access of its own.
	Caller CallerIdentity
	// IdentityForwardHosts is the OPERATOR-sanctioned set of MCP endpoint hosts
	// that may receive Caller — matched exactly and as a domain suffix. Kept
	// separate from the SSRF allow-list on purpose: that one answers "may we
	// connect at all", this the far narrower "may we hand this endpoint the
	// user's credential". Empty (the default) forwards to nobody. See identity.go.
	IdentityForwardHosts []string
	// ExpectedProject, when set, is the namespace/project of the calling request.
	// It is a defense-in-depth tenant-isolation check on the capability Source:
	// the Source is responsible for returning only the calling project's
	// documents, but any document whose Metadata.Namespace disagrees with
	// ExpectedProject is dropped and logged rather than trusted. Documents that
	// carry no namespace are passed through — for those the Source remains the
	// scoping authority (the CRD projection has no spec-level project field to
	// cross-check; if one is added later, extend ScopeDocuments to verify it).
	ExpectedProject string
	// Memory, when non-nil, enables the memory_remember / memory_forget
	// built-in tools (see internal/capability/memory.go) scoped to
	// ExpectedProject, plus a "Project memory:" addendum section listing the
	// project's current facts. Nil disables the feature entirely — no tools,
	// no addendum section. Also requires ExpectedProject to be set, since the
	// tools need a project to scope reads/writes to.
	Memory memory.Store
	// ContextID, when set, is the conversation this composition is running
	// for. It is threaded into gap reports as provenance only (see
	// GapReports) — it plays no role in tool scoping or tenant isolation.
	ContextID string
	// GapReports, when non-nil, enables one report_capability_gap__<service>
	// tool per composed document that declares spec.reportingProject (see
	// internal/capability/gapreport.go): the model can flag that a provider
	// service is missing a tool or lookup a user needed, and the report is
	// written to that PROVIDER's own project — never ExpectedProject, the
	// consumer's project — so the team that owns the missing capability sees
	// it without the user having to report it themselves. A document with
	// tools but no reportingProject simply gets no gap-report tool. Nil
	// disables the feature entirely.
	GapReports gapreport.Store
	// PlatformAPI, when non-nil, adds the base platform tools (see
	// internal/basetools) to every project's composition: read this project's
	// resources of any kind, describe a kind, list where a service is offered,
	// report what the allowance has left. They are not one provider's
	// contribution, so they are not namespaced under a service and are not
	// allow-listed by a capability document — they are what every project has.
	//
	// They run as the CALLER and only as the caller: the client is bound to
	// Caller.BearerToken and ExpectedProject here, once, and the tools receive
	// a view that carries no other identity. With either missing there is
	// nobody to act as, so nothing is composed. Nil disables the feature.
	PlatformAPI *projectapi.Client
	// PlanTokenKey binds plans for the base tools' change path
	// (resources_validate, resources_plan, resources_apply). Empty leaves the
	// change path out: a service that cannot check a token must not issue one.
	// Ignored when PlatformAPI is nil. See internal/plantoken.
	PlanTokenKey []byte
	// Metrics, when non-nil, records assistant_gap_report_total for every
	// report_capability_gap tool call this composition creates (see
	// internal/metrics). Nil disables recording only — GapReports still
	// governs whether the tool is composed at all.
	Metrics *appmetrics.Metrics
	// contains filtered or unexported fields
}

ComposeOptions configures Compose. All fields are optional.

type Composed

type Composed struct {
	// SystemPromptAddendum is "" when no document contributed knowledge.
	SystemPromptAddendum string
	// Tools holds the allow-listed provider tools, keyed and named
	// "<server>__<tool>", together with the platform's own built-ins.
	Tools agentcore.ToolSet
	// Mutating names the composed tools on a change path: those a capability
	// document flagged in mcpServers[].mutating, plus the base tools' change
	// path. It answers what this project's assistant can change. Sorted, so
	// two compositions of the same project read alike.
	Mutating []string
	// contains filtered or unexported fields
}

Composed is the result of Compose: the knowledge addendum for the system prompt and the allow-listed, namespaced provider tools ready to drive the loop. Close tears down the per-request MCP sessions.

func Compose

func Compose(ctx context.Context, docs []CapabilityDocument, opts ComposeOptions) (*Composed, error)

Compose turns a project's capability documents into composed capabilities: it fetches knowledge into a provenance-labelled addendum and connects each document's MCP servers, exposing only the allow-listed tools, namespaced and de-collided. A server that cannot be reached (or a tool that is missing) contributes nothing and is logged — it never fails the whole composition.

func (*Composed) Close

func (c *Composed) Close() error

Close closes every MCP session opened during composition. It is safe to call more than once.

func (*Composed) IsMutating

func (c *Composed) IsMutating(name string) bool

IsMutating reports whether a composed tool is on a change path.

type Condition

type Condition struct {
	Type    string `json:"type"`
	Status  string `json:"status"`
	Reason  string `json:"reason,omitempty"`
	Message string `json:"message,omitempty"`
}

Condition is a status condition on the document.

type FixtureSource

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

FixtureSource is a Source backed by a JSON file of capability documents — the output of a project-scoped export. The file path is injected (not read from the environment) so the type stays env-free and testable.

func NewFixtureSource

func NewFixtureSource(path string, logger *slog.Logger) *FixtureSource

NewFixtureSource returns a fixture source reading path. A nil logger discards skip warnings.

func (*FixtureSource) Documents

func (s *FixtureSource) Documents(_ context.Context, _ string) ([]CapabilityDocument, error)

Documents reads and parses the fixture file. The export is already project-scoped, so projectName does not filter here (that is the HTTP source's job). Individual documents that fail validation are skipped with a warning; a missing file or malformed root is an error.

type GVKRef

type GVKRef struct {
	Group string `json:"group"`
	Kind  string `json:"kind"`
}

GVKRef is the {group, kind} reference style used across the service catalog (no version).

type HTTPSource

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

HTTPSource is a Source backed by the capability-provider HTTP API. For each project it fetches

GET {baseURL}/projects/{projectName}/capability-documents

and parses the response with the same validation path as FixtureSource (unknown fields ignored, individual invalid documents skipped with a warning). There is no caching in v0 — every call performs a fresh fetch.

The source degrades rather than fails: any transport error, non-2xx status, body-read error, or malformed root JSON is logged and yields an empty document set (nil error). That matches the assistant's fixture-missing behavior — the agent composes built-ins only — so a provider outage can never fail a chat. It never panics.

func NewHTTPSource

func NewHTTPSource(baseURL string, client *http.Client, logger *slog.Logger) *HTTPSource

NewHTTPSource returns an HTTP source targeting baseURL (the capability provider's base URL, e.g. http://capability-adapter). A trailing slash is tolerated. A nil client uses a default client with a 5s timeout; a nil logger discards degradation warnings.

func (*HTTPSource) Documents

func (s *HTTPSource) Documents(ctx context.Context, projectName string) ([]CapabilityDocument, error)

Documents fetches and parses projectName's capability documents. On any failure it logs and returns an empty set (nil error) — see HTTPSource.

type Knowledge

type Knowledge struct {
	Sources  []KnowledgeSource  `json:"sources,omitempty"`
	Concepts []KnowledgeConcept `json:"concepts,omitempty"`
}

Knowledge is the Tier-1 knowledge a provider contributes.

type KnowledgeConcept

type KnowledgeConcept struct {
	GVK     GVKRef `json:"gvk"`
	Summary string `json:"summary"`
}

KnowledgeConcept is a short, provider-authored gloss on one of its resource kinds.

type KnowledgeSource

type KnowledgeSource struct {
	Type  KnowledgeSourceType `json:"type"`
	Title string              `json:"title,omitempty"`
	URL   string              `json:"url"`
}

KnowledgeSource is a fetchable provider document.

type KnowledgeSourceType

type KnowledgeSourceType string

KnowledgeSourceType is the kind of a knowledge source document.

const (
	KnowledgeLLMDocs  KnowledgeSourceType = "LLMDocs"
	KnowledgeRunbook  KnowledgeSourceType = "Runbook"
	KnowledgeMarkdown KnowledgeSourceType = "Markdown"
)

type MCPServer

type MCPServer struct {
	Name         string       `json:"name"`
	Endpoint     string       `json:"endpoint"`
	ToolSelector ToolSelector `json:"toolSelector"`
	Mutating     []string     `json:"mutating,omitempty"`
}

MCPServer is one provider MCP server the assistant may connect to.

type Metadata

type Metadata struct {
	Name      string `json:"name,omitempty"`
	Namespace string `json:"namespace,omitempty"`
}

Metadata mirrors the object metadata carried on the CRD projection.

type ProviderToolInvocation

type ProviderToolInvocation struct {
	// ServiceName is the reverse-DNS provider service name from the document.
	ServiceName string
	// ServerName is the mcpServers[].name shortname the tool is namespaced under.
	ServerName string
	// ToolName is the original (un-namespaced) provider tool name.
	ToolName string
	// NamespacedToolName is the name the model sees, "<server>__<tool>".
	NamespacedToolName string
}

ProviderToolInvocation is reported once per provider-tool execution (the metering hook). It identifies the provider service and both the raw and namespaced tool names.

type Ref

type Ref struct {
	Name string `json:"name"`
}

Ref is a by-name object reference.

type ServiceEntitlement

type ServiceEntitlement struct {
	// ID is the sanitized serviceRef: the stable identifier a card advertises,
	// run through [SanitizeName] so a provider-supplied name cannot put an
	// arbitrary string in a field consumers treat as an ID.
	ID                string
	ServiceRef        string   // spec.serviceRef.name, verbatim — "streamco"
	ServiceName       string   // spec.serviceName — "streaming.streamco.example"
	ToolNames         []string // namespaced "<server>__<tool>", from ToolSelector.Include ONLY
	MCPEndpoints      []string // spec.tools.mcpServers[].endpoint
	SkillNames        []string // namespaced "<serviceRef>__<skill>"
	SkillDescriptions []string
	KnowledgeTitles   []string // knowledge source titles (or URLs when untitled)
}

ServiceEntitlement is one entitled provider service, derived STRICTLY from that project's capability documents — nothing global, nothing inferred, and nothing learned from a live MCP connection.

func Entitlements

func Entitlements(docs []CapabilityDocument) []ServiceEntitlement

Entitlements derives one entry per entitled service from ALREADY-SCOPED documents. Callers MUST pass the output of ScopeDocuments: a Source may return more than the calling project's documents (FixtureSource ignores the project entirely), and that gate is the only thing standing between a project's card and another project's services.

It reads documents, never a Composed, so Patch's own built-ins (load_skill, memory_remember, memory_forget, report_capability_gap__*) can never leak into what is advertised as provider surface. It performs no MCP connect either: the declared ToolSelector.Include list is what a project is entitled to, which is a claim about entitlement, not about health.

type Skill

type Skill struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	// Source is the HTTP(S) URL of the skill body (markdown/plain text).
	Source string `json:"source"`
}

Skill is a provider-published, reviewed procedure the assistant may follow — the middle rung between knowledge (facts) and tools (callable endpoints). Only Name and Description enter the prompt; the body at Source is fetched on demand via the built-in load_skill tool (progressive disclosure), so a provider can publish many skills at near-zero prompt cost. A skill never grants privileges: it can only direct the model toward tools that are independently allow-listed.

type Source

type Source interface {
	// Documents returns the capability documents entitling projectName.
	Documents(ctx context.Context, projectName string) ([]CapabilityDocument, error)
}

Source supplies the capability documents that apply to a project. The local slice uses FixtureSource (a JSON file exported from the control plane); a production HTTP-backed source is a follow-up behind this same seam.

type Status

type Status struct {
	Conditions []Condition `json:"conditions,omitempty"`
}

Status carries the document's status conditions.

type ToolSelector

type ToolSelector struct {
	Include []string `json:"include,omitempty"`
}

ToolSelector is the client-side allow-list of tool names to expose from an MCP server.

type Tools

type Tools struct {
	MCPServers []MCPServer `json:"mcpServers,omitempty"`
}

Tools is the Tier-2 tool surface a provider contributes.

Jump to

Keyboard shortcuts

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