Documentation
¶
Overview ¶
Package skills owns Harbor's token-savvy, DB-backed, identity- scoped skill subsystem (RFC §6.7). lands the leaf surface:
- The mandatory `SkillStore` interface every backend implements.
- The shared types — `Skill`, `Origin`, `Scope`, `ListFilter`, `RankedSkill`.
- Sentinel errors compared via `errors.Is`.
- The §4.4 extensibility-seam plumbing (registry + factory).
sibling layers (planner-facing tools, the virtual directory, the Skills.md importer, the in-runtime generator with persistence) all consume this surface.
Identity is mandatory at every method. The triple `(tenant, user, session)` MUST be fully populated; empty `RunID` is accepted (skills are session-scoped at the storage layer; the generator stamps `OriginRef` with the run id from `ctx`). Missing-triple operations fail closed with `ErrIdentityRequired` AND emit a `skill.identity_rejected` event on the bus — never silent (AGENTS.md §5 "Fail loudly").
Concurrent reuse: one `SkillStore` instance is safe to share across N concurrent goroutines. Drivers persist only internally-synchronized state on themselves; per-call state lives in `ctx` and the supplied `identity.Quadruple`.
Index ¶
- Constants
- Variables
- func CanonicalContentHash(s Skill) string
- func EmitIdentityRejected(ctx context.Context, bus events.EventBus, q identity.Quadruple, ...) error
- func IdentityFromCtx(ctx context.Context) (identity.Quadruple, error)
- func Register(name string, factory Factory)
- func RegisteredDrivers() []string
- func ValidateIdentity(q identity.Quadruple) error
- type ConfigSnapshot
- type Deps
- type Directory
- type DirectoryCapability
- type DirectoryConfig
- type Embedder
- type Factory
- type ListFilter
- type Origin
- type RankedSkill
- type RetrievalMode
- type Scope
- type Selection
- type Skill
- type SkillDeletedPayload
- type SkillIdentityRejectedPayload
- type SkillPackOverwriteRefusedPayload
- type SkillSearchExecutedPayload
- type SkillStore
- type SkillUpsertedPayload
- type SkillView
Constants ¶
const ( // DefaultMaxEntries is the per-View row cap when DirectoryConfig // leaves MaxEntries at zero. DefaultMaxEntries = 30 // MinMaxEntries is the inclusive lower bound for MaxEntries. MinMaxEntries = 1 // MaxMaxEntries is the inclusive upper bound for MaxEntries. MaxMaxEntries = 200 )
MaxEntries bounds — a binding contract.
const ( // PathFTS5 — FTS5 virtual table produced the row. PathFTS5 = "fts5" // PathRegex — regex fallback produced the row. PathRegex = "regex" // PathExact — exact lowercase-equality fallback produced the row. PathExact = "exact" // PathSemantic — the opt-in semantic retrieval mode produced the // row: embedding-similarity ranking over the identity-scoped // catalog (RetrievalSemantic). PathSemantic = "semantic" )
Search-result paths.
const DefaultDriver = "localdb"
DefaultDriver is the production driver name. later phases (Portico) registers additional names.
const EventTypeSkillDeleted events.EventType = "skill.deleted"
EventTypeSkillDeleted is emitted on a successful `Delete`. Payload carries the canonical identifiers. SafePayload by construction.
const EventTypeSkillIdentityRejected events.EventType = "skill.identity_rejected"
EventTypeSkillIdentityRejected is emitted when a `SkillStore` method is called with a missing identity triple (fail- closed contract). Mirrors `memory.identity_rejected` for the skills subsystem.
const EventTypeSkillPackOverwriteRefused events.EventType = "skill.pack_overwrite_refused"
EventTypeSkillPackOverwriteRefused is emitted when an `Upsert` would have overwritten an `Origin=pack` row with non-pack input. The store does NOT mutate the row; the emit makes the refusal observable from Console / audit. SafePayload by construction.
const EventTypeSkillProposed events.EventType = "skill.proposed"
EventTypeSkillProposed is the mandatory audit event emitted by the in-runtime skill generator (`skill_propose(persist=true)`) on every persist — whether the persist succeeded (`persisted`), was idempotent (`idempotent`), or was rejected by the conflict policy (`rejected`). Caller-controlled string fields (Title / Trigger excerpts) are run through `audit.Redactor.Redact` BEFORE the event is built (RFC §6.7). The payload itself is SafePayload so the bus does not re-run it through the redactor; the generator is the authoritative redaction point.
const EventTypeSkillSearchExecuted events.EventType = "skill.search_executed"
EventTypeSkillSearchExecuted is emitted on every `Search` call. Payload carries the query (HASHED — the search corpus is allowed to contain caller bytes per RFC §6.7, but the audit emit MUST NOT surface them), the path that produced the result, and the row count. SafePayload by construction.
const EventTypeSkillUpserted events.EventType = "skill.upserted"
EventTypeSkillUpserted is emitted on every successful `Upsert`. Payload carries the canonical skill identifiers + `Idempotent` flag so subscribers can distinguish a true write from a hash- matched no-op. SafePayload by construction — every field is a bounded enumerable string or a boolean; no caller-controlled bytes survive on the payload.
const ExtraPinnedKey = "pinned"
ExtraPinnedKey is the Skill.Extra map key the directory treats as a runtime-stamped pin marker. Value MUST be the bool true; any other shape is ignored (no string "true" / int 1 / etc. — fail-loud on shape drift rather than silently accept).
Variables ¶
var ( // ErrSkillNotFound — `Get` / `Delete` against a non-existent // row. ErrSkillNotFound = errors.New("skills: skill not found") // ErrPackOverwriteRefused — `Upsert` attempted to overwrite an // `Origin=pack` row with non-pack input. ErrPackOverwriteRefused = errors.New("skills: refuse to overwrite pack-origin skill") // ErrStoreClosed — store has been closed; further operations // are rejected. ErrStoreClosed = errors.New("skills: store is closed") // ErrInvalidSkill — supplied `Skill` failed validation. ErrInvalidSkill = errors.New("skills: invalid skill") // ErrUnknownDriver — `Open` was asked for a driver name no // factory has been registered under. ErrUnknownDriver = errors.New("skills: unknown driver") // ErrIdentityRequired — caller passed a `Quadruple` with at // least one empty `(tenant, user, session)` component. The // store also emits `skill.identity_rejected` on the bus. ErrIdentityRequired = errors.New("skills: identity triple incomplete") )
Sentinel errors. Compare via `errors.Is`.
var ErrInvalidConfig = errors.New("skills: invalid directory config")
ErrInvalidConfig — NewDirectory rejected the DirectoryConfig. Returned wrapped via fmt.Errorf("%w: ...") so callers extract the cause via errors.Is. Fail-loud per CLAUDE.md §5 + §13.
Functions ¶
func CanonicalContentHash ¶
CanonicalContentHash returns the canonical sha256 hex of `s`'s content fields. The hash is the LWW gate and the idempotency key for `Upsert`:
- Origin / OriginRef / Scope / ScopeTenantID / ScopeProjectID are EXCLUDED: the same skill imported via two paths (PackImport from a re-published pack vs. Generated from a planner re-derivation) hashes identically when the content is the same, so the conflict-policy gate fires on actual content drift rather than provenance noise.
- Lifecycle timestamps (`CreatedAt`, `UpdatedAt`, `LastUsed`) and `UseCount` are EXCLUDED: they evolve over the row's life without semantic content change.
- `Extra` IS INCLUDED via its canonical JSON-key-sorted text representation (the generator may stamp model metadata there that legitimately participates in identity).
The hash is computed over a fixed-key envelope, NOT over a struct rendering, so future field additions don't silently break stored hashes. The envelope format and the included-fields list ARE load-bearing — changes need a new content-hash version.
Slice-shaped fields (`Tags`, `RequiredTools`, `RequiredNS`, `RequiredTags`) are sorted before hashing so ordering noise from caller-side normalisation doesn't perturb the hash. Ordered slices (`Steps`, `Preconditions`, `FailureModes`) preserve their order because the planner's procedural rendering depends on it.
func EmitIdentityRejected ¶
func EmitIdentityRejected(ctx context.Context, bus events.EventBus, q identity.Quadruple, operation string) error
EmitIdentityRejected publishes the `skill.identity_rejected` event on `bus` and returns the wrapped `ErrIdentityRequired`. Drivers call this from every method that detected an identity- rejection so the audit emit path is consistent across implementations.
The bus's `ValidateEvent` requires a fully-populated identity triple; the partial / empty triple the caller supplied is preserved where present and substituted with the `missingIdentitySentinel` string elsewhere, so the rejection event itself is bus-publishable. The audit-visible payload's `Reason` field names the component(s) that were actually missing.
`operation` is the rejected method name ("Upsert", "Get", "Search", "Delete", "List", "Close").
If Publish fails, the wrapped error names both the rejection cause and the bus failure — callers MUST NOT silently drop either.
func IdentityFromCtx ¶
IdentityFromCtx reads the identity `Quadruple` (or bare `Identity`) from `ctx` and validates the triple. It returns the (possibly partial) Quadruple plus a wrapped `ErrIdentityRequired` on failure; the partial value is what callers pass to `EmitIdentityRejected`, which substitutes the missing-component sentinel for the bus's `ValidateEvent` triple check.
Single source for the planner tools (`internal/skills/tools`) and the virtual directory (`internal/skills`) — both consumed the same byte-for-byte logic before this was hoisted here.
func Register ¶
Register installs a driver factory under `name`. Drivers self- register from their package `init()`; `cmd/harbor` blank-imports the production driver to trigger registration. Per AGENTS.md §4.4.
Re-registering the same name panics — the registration model is write-once-at-init and a duplicate signals a build mis-config.
func RegisteredDrivers ¶
func RegisteredDrivers() []string
RegisteredDrivers returns a sorted list of driver names. Useful for boot-log emission and for surfacing in error messages.
func ValidateIdentity ¶
ValidateIdentity returns wrapped `ErrIdentityRequired` when any of `(TenantID, UserID, SessionID)` on `q` is empty. `RunID` is allowed to be empty (skills are session-scoped at the storage layer). Mirrors `memory.ValidateIdentity` for the identity- mandatory contract.
Types ¶
type ConfigSnapshot ¶
type ConfigSnapshot struct {
// Driver names the registered factory. Empty → DefaultDriver.
Driver string
// DSN is consumed by the `localdb` driver. Bare file path or
// SQLite `file:` URI; the special `:memory:` sentinel is
// honoured for tests. `secret:"true"` redaction lives at the
// config-package boundary.
DSN string
// Retrieval opts in to a `Search` ranking mode. The zero value
// keeps the FTS5 → regex → exact ladder; `RetrievalSemantic`
// ranks by embedding similarity (requires `Deps.Embedder`).
Retrieval RetrievalMode
}
ConfigSnapshot is the strict subset of `config.SkillsConfig` the skills package consumes. Keeping a snapshot decouples drivers from the config package's type evolution.
func SnapshotFromConfig ¶ added in v1.3.0
func SnapshotFromConfig(cfg config.SkillsConfig) ConfigSnapshot
SnapshotFromConfig projects the operator-facing `config.SkillsConfig` block onto the skills package's decoupled ConfigSnapshot. Every config field maps 1:1; the field-parity test in from_config_test.go fails the build when a new config field lands without a projection (or an explicit exclusion naming why). `Directory` is excluded — the store snapshot and the directory config feed two different constructors; `DirectoryFromConfig` is the directory's projection.
type Deps ¶
Deps carries the runtime dependencies a skills driver needs.
`Bus` is mandatory so identity-rejection emits + audit events (`skill.upserted`, `skill.deleted`, `skill.pack_overwrite_refused`, `skill.search_executed`) land on the audit pipeline.
**Note** (analog): unlike memory drivers, skills drivers do NOT receive a `state.StateStore`. The `localdb` driver owns its own `skills` + `skills_fts` tables; persistent skill state lives in the driver's own DB, not piggybacked onto the StateStore. The Portico driver (post-V1) will fetch from a remote MCP surface and also has no StateStore need. `Embedder` is the injectable text→vector callable the semantic retrieval mode consumes. OPTIONAL — required only when `cfg.Retrieval == RetrievalSemantic`, ignored otherwise. A semantic config without an Embedder fails loudly at `Open` (mirroring the memory registry's Summarizer/Embedder rule) — never a stub fallback (AGENTS.md §13). Existing callers that construct `Deps{Bus}` keep compiling: the zero value is nil, valid for the default retrieval mode.
type Directory ¶
type Directory struct {
// contains filtered or unexported fields
}
Directory exposes the virtual-directory snapshot. Built once at boot (or per operator-config reload); safe to share across N concurrent goroutines. Per-call state lives on ctx + the args; no mutable field on Directory itself changes after construction.
func NewDirectory ¶
func NewDirectory(store SkillStore, deps Deps, cfg DirectoryConfig) (*Directory, error)
NewDirectory validates cfg and returns a usable Directory.
Validation rules:
- store == nil → wrapped ErrInvalidConfig
- deps.Bus == nil → wrapped ErrInvalidConfig
- cfg.MaxEntries == 0 → DefaultMaxEntries (30)
- cfg.MaxEntries < 1 or > 200 → wrapped ErrInvalidConfig
- cfg.Selection == "" → SelectionPinnedThenRecent
- cfg.Selection not in the two canonical values → wrapped ErrInvalidConfig
Concurrent reuse: the returned *Directory holds no mutable state; safe to share across N goroutines.
func (*Directory) View ¶
View returns the identity-scoped, capability-filtered, redacted, bounded snapshot of the skill catalogue.
Pipeline:
- Read identity from ctx (Quadruple or Identity); missing → wrapped ErrIdentityRequired + skill.identity_rejected emit.
- SkillStore.List for the identity (Limit=0 — driver default; the directory bounds locally to maxEntry afterwards).
- Capability filter: exclude skills whose RequiredTools / RequiredNS / RequiredTags are NOT subsets of cap.Allowed*.
- Partition by pinning (config-declared first, then Extra-pinned, then unpinned).
- Sort each partition by the Selection's secondary key (UpdatedAt DESC, Name ASC for pinned_then_recent; UseCount DESC, Name ASC for pinned_then_top). Config-declared pins preserve declaration order.
- Concatenate pinned ++ unpinned; truncate to maxEntry.
- Project to SkillView, redacting Title + Trigger.
Concurrent-safe: pure read pipeline over the immutable *Directory. Per-call state lives on ctx + cap; no shared mutable state.
type DirectoryCapability ¶
type DirectoryCapability struct {
// AllowedTools — the set of tool names visible to the run.
// Used by the filter (subset check) AND the redactor (skills
// referencing names not in this set get the disallowed-name
// replacement in Title / Trigger).
AllowedTools []string `json:"allowed_tools,omitempty"`
// AllowedNamespaces — namespaces (Skill.RequiredNS) the run may
// reference.
AllowedNamespaces []string `json:"allowed_namespaces,omitempty"`
// AllowedTags — tags (Skill.RequiredTags) the run may reference.
AllowedTags []string `json:"allowed_tags,omitempty"`
}
DirectoryCapability mirrors the planner-supplied capability envelope the directory consults for filtering + redaction at View time. The shape is identical to internal/skills/tools.CapabilityContext; duplicating the type here avoids the import cycle (`internal/skills/ tools` imports `internal/skills` already). When a caller already holds a `tools.CapabilityContext`, the fields map one-to-one and the conversion is mechanical at the planner boundary.
Concurrent reuse: the struct is a value carried on the args; never mutated in-flight. Multiple goroutines may share a DirectoryCapability value safely.
type DirectoryConfig ¶
type DirectoryConfig struct {
// Pinned is the operator-declared list of skill names to anchor
// at the top of every View. Order is preserved across calls.
// Names that don't exist under the calling identity are dropped
// silently (config + storage may legitimately disagree; storage
// wins).
Pinned []string
// MaxEntries caps the View length. 0 → DefaultMaxEntries (30).
// Outside [1, 200] → NewDirectory returns wrapped
// ErrInvalidConfig.
MaxEntries int
// Selection picks the unpinned-partition ordering. Empty →
// SelectionPinnedThenRecent.
Selection Selection
}
DirectoryConfig configures one Directory instance.
func DirectoryFromConfig ¶ added in v1.3.0
func DirectoryFromConfig(cfg config.SkillsConfig, fallbackMaxEntries int) DirectoryConfig
DirectoryFromConfig projects the operator-facing `config.SkillsConfig.Directory` block onto a DirectoryConfig for `NewDirectory`.
`fallbackMaxEntries` is consulted when `skills.directory.max_entries` is unset (zero): the run-loop callers pass the resolved `planner.skills_context_max` value so the pre-111d injection-budget knob keeps capping the injected block's length. A zero fallback leaves MaxEntries at zero and `NewDirectory` applies its own package default (30). The Pinned slice is copied so the caller's config struct cannot mutate the directory after construction.
type Embedder ¶ added in v1.4.0
Embedder is the injectable text→vector callable the semantic retrieval mode consumes — the consumer-side-interface shape the memory subsystem's `Summarizer` established. The canonical implementation is constructed via the embeddings factory (`internal/embeddings`); any `embeddings.Embedder` satisfies this interface.
Concurrent-reuse contract: one `Embedder` instance is safe to share across N concurrent goroutines. Implementers MUST honour `ctx.Done()`.
type Factory ¶
type Factory func(cfg ConfigSnapshot, deps Deps) (SkillStore, error)
Factory builds a `SkillStore` from a `ConfigSnapshot` + `Deps`. Drivers expose one `Factory` each via `init()` → `Register`.
type ListFilter ¶
type ListFilter struct {
Scope Scope
TaskType string
Tags []string // any-of match against the skill's `Tags`
Limit int
Offset int
}
ListFilter narrows the rows `List` returns. Zero-value fields are matched as "any". Drivers cap `Limit` at 1000; `Limit == 0` falls back to the driver default (100).
type Origin ¶
type Origin string
Origin is the provenance of a skill — the pack import path or the in-runtime generator path. The two flavors share storage but their conflict semantics differ: a `Generated` skill MAY NOT overwrite a `PackImport` skill of the same name.
const ( // OriginPack — imported from a Skills.md pack (lands the // importer). Pack rows are immutable from the generator's // perspective: `Upsert` refuses to overwrite with // `ErrPackOverwriteRefused` when incoming Origin != OriginPack. OriginPack Origin = "pack" // OriginGenerated — produced by the in-runtime generator // (`skill_propose(persist=true)`). Generated→Generated // is last-write-wins gated by `ContentHash` change. OriginGenerated Origin = "generated" )
Origin values.
type RankedSkill ¶
RankedSkill carries the search-time relevance score + the path that produced it. `Path` is one of `"fts5" | "regex" | "exact"`; callers (the planner tools) surface it for observability only — it is not part of the ranking math.
`Score` is the normalised 0.0–1.0 score:
- FTS5 path: `bm25 → 1/(1+raw) → min-max normalised`.
- Regex path: `name fullmatch=0.95 | name match=0.90 | name search=0.85 | body search=0.75`.
- Exact path: 1.0 (lowercase equality on `name | title | trigger | tags`).
type RetrievalMode ¶ added in v1.4.0
type RetrievalMode string
RetrievalMode declares the opt-in `Search` ranking shape.
const ( // RetrievalDefault — the zero value: the token-savvy FTS5 → // regex → exact ladder. RetrievalDefault RetrievalMode = "" // RetrievalSemantic — rank by embedding similarity over the // identity-scoped catalog (result path `PathSemantic`). Requires // `Deps.Embedder` — no stub fallback. Capability filtering, // redaction, and the budgeter apply unchanged downstream. RetrievalSemantic RetrievalMode = "semantic" )
RetrievalMode values.
type Scope ¶
type Scope string
Scope is the operator-declared visibility of a skill.
const ( // ScopeSession — visible only inside the originating session. // The narrowest scope; cross-session visibility requires an // explicit promotion (RFC §6.7 "isolation // conformance"). The storage layer's identity filter already // pins rows to a `(tenant, user, session)` triple; ScopeSession // is the matching declared-scope marker for rows that should // stay session-local. ScopeSession Scope = "session" // ScopeProject — visible inside the operator-declared project // only. The generator default per RFC §6.7. ScopeProject Scope = "project" // ScopeTenant — visible to every project inside the same tenant. ScopeTenant Scope = "tenant" // ScopeGlobal — visible to every tenant. Reserved for operator- // managed shared skills. ScopeGlobal Scope = "global" )
Scope values.
type Selection ¶
type Selection string
Selection picks the unpinned-partition ordering. Pinned skills always come first; Selection orders only the unpinned remainder (and the secondary ordering inside the Extra-pinned subset).
const ( // SelectionPinnedThenRecent — pinned first, then most-recently- // updated skills (UpdatedAt DESC, Name ASC). SelectionPinnedThenRecent Selection = "pinned_then_recent" // SelectionPinnedThenTop — pinned first, then most-used skills // (UseCount DESC, Name ASC). Library-level only at V1.1.x: no // production path increments UseCount, so the OPERATOR config // validator rejects `skills.directory.selection: pinned_then_top` // as not-yet-wired. An // embedder that maintains UseCount through Upsert itself may use // this Selection directly. SelectionPinnedThenTop Selection = "pinned_then_top" )
Selection values.
type Skill ¶
type Skill struct {
Name string
Title string
Description string
Trigger string
TaskType string
Tags []string
Steps []string
Preconditions []string
FailureModes []string
RequiredTools []string
RequiredNS []string
RequiredTags []string
Origin Origin
OriginRef string
Scope Scope
ScopeTenantID string
ScopeProjectID string
ContentHash string
CreatedAt time.Time
UpdatedAt time.Time
LastUsed time.Time
UseCount int
Extra map[string]any
}
Skill is the canonical skill record. The struct is the storage envelope drivers persist; the planner-facing tools wrap it with capability filtering + redaction at injection time.
Validation rules at `validate`:
- `Name` non-empty
- `Trigger` non-empty (planner-visible match cue)
- `Steps` non-empty (at least one step)
- `Origin` ∈ {OriginPack, OriginGenerated}
- `Scope` ∈ {ScopeSession, ScopeProject, ScopeTenant, ScopeGlobal}
type SkillDeletedPayload ¶
type SkillDeletedPayload struct {
events.SafeSealed
Name string
}
SkillDeletedPayload reports a successful delete. SafePayload by construction.
type SkillIdentityRejectedPayload ¶
type SkillIdentityRejectedPayload struct {
events.SafeSealed
Operation string
Reason string
}
SkillIdentityRejectedPayload mirrors `memory.MemoryIdentityRejectedPayload`. Operation is the rejected method name ("Upsert", "Get", "Search", "Delete", "List"); Reason is a short static string indicating which component was missing.
type SkillPackOverwriteRefusedPayload ¶
type SkillPackOverwriteRefusedPayload struct {
events.SafeSealed
Name string
ExistingOrigin Origin
IncomingOrigin Origin
}
SkillPackOverwriteRefusedPayload reports a refused upsert against an existing `Origin=pack` row. `IncomingOrigin` names the offender; the store left the row untouched. SafePayload by construction.
type SkillSearchExecutedPayload ¶
type SkillSearchExecutedPayload struct {
events.SafeSealed
QueryHash string // first 16 hex chars of sha256(lowercased query)
Path string // "fts5" | "regex" | "exact"
Limit int
ResultN int
}
SkillSearchExecutedPayload reports a `Search` execution. `QueryHash` is a sha256 hex prefix of the lowercased query so repeated identical searches are correlatable for tracing without leaking the raw text. SafePayload by construction.
type SkillStore ¶
type SkillStore interface {
// Upsert inserts or updates `skill` under the identity-scoped
// `(tenant, user, session, scope, name)` key. Conflict policy
// (RFC §6.7):
//
// - existing.Origin == "pack" && skill.Origin != "pack" →
// `ErrPackOverwriteRefused` AND
// `skill.pack_overwrite_refused` emit. Row left untouched.
// - existing.Origin == "generated" && skill.Origin ==
// "generated" && existing.ContentHash == skill.ContentHash →
// idempotent no-op; emit a single `skill.upserted` for
// observability with `idempotent=true` payload field.
// - otherwise: last-write-wins; emit `skill.upserted` with
// `idempotent=false`.
Upsert(ctx context.Context, id identity.Quadruple, skill Skill) error
// Get returns the skill identified by `name` under the supplied
// identity. Missing → `ErrSkillNotFound`.
Get(ctx context.Context, id identity.Quadruple, name string) (Skill, error)
// List returns the filtered, paged skills under the supplied
// identity. Ordering is deterministic: `(UpdatedAt DESC,
// Name ASC)`.
List(ctx context.Context, id identity.Quadruple, filter ListFilter) ([]Skill, error)
// Search returns up to `limit` skills ranked by the FTS5 →
// regex → exact ladder. `limit == 0` falls back
// to 20. The driver picks the first path that returns rows;
// later paths run only when earlier ones produced nothing.
// Emits `skill.search_executed` with the path that produced the
// result.
Search(ctx context.Context, id identity.Quadruple, query string, limit int) ([]RankedSkill, error)
// Delete removes the named skill under the identity. Missing →
// `ErrSkillNotFound`. Emits `skill.deleted` on success.
Delete(ctx context.Context, id identity.Quadruple, name string) error
// Close releases the driver's resources. Subsequent method
// calls return `ErrStoreClosed`. Close is idempotent.
Close(ctx context.Context) error
}
SkillStore is Harbor's mandatory skill-storage interface. A single surface; every V1 driver (`localdb` here; Portico post-V1) implements every method. No `Supports*` ceremony per AGENTS.md §4.4.
Identity-mandatory contract:
- Every method validates the identity `Quadruple` at the boundary. Empty tenant / user / session returns wrapped `ErrIdentityRequired` AND emits one `skill.identity_rejected` event on the bus.
Concurrent-reuse contract:
- One instance is safe to share across N concurrent goroutines. Mutable state is internally synchronised; per-call state lives in `ctx` and the supplied `Quadruple`, never on the driver.
func Open ¶
func Open(_ context.Context, cfg ConfigSnapshot, deps Deps) (SkillStore, error)
Open returns the `SkillStore` built by the factory whose name matches `cfg.Driver` (defaults to `DefaultDriver` when empty).
Deps are validated: a missing EventBus returns a wrapped error before the factory runs — fail loudly, never silently degrade.
func OpenDriver ¶
func OpenDriver(name string, cfg ConfigSnapshot, deps Deps) (SkillStore, error)
OpenDriver opens a specific driver by name; useful for tests that want to exercise the registry against a non-default driver.
type SkillUpsertedPayload ¶
type SkillUpsertedPayload struct {
events.SafeSealed
Name string
Origin Origin
Scope Scope
ContentHash string
Idempotent bool // true → existing row's hash matched; no write occurred
}
SkillUpsertedPayload reports a successful upsert. SafePayload by construction — all fields are bounded enumerable strings / ints / booleans; no caller-controlled bytes leak through.
type SkillView ¶
type SkillView struct {
Name string `json:"name"`
Title string `json:"title,omitempty"`
Trigger string `json:"trigger,omitempty"`
TaskType string `json:"task_type,omitempty"`
// Pinned is true when the skill was anchored by either
// DirectoryConfig.Pinned (config-time) or Skill.Extra["pinned"]
// (runtime-time). Surfaced for Console rendering; inert to the
// planner's reasoning.
Pinned bool `json:"pinned"`
}
SkillView is the planner-visible projection of a Skill returned by Directory.View. Four content fields plus a Pinned marker; the rest of the Skill is dropped at the boundary so the View is cheap to inject and Console-renderable without leaking storage-layer detail.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package capfilter holds the primitive capability-filter and tool-name-scrub logic shared by the skills planner tools (internal/skills/tools) and the virtual directory (internal/skills).
|
Package capfilter holds the primitive capability-filter and tool-name-scrub logic shared by the skills planner tools (internal/skills/tools) and the virtual directory (internal/skills). |
|
Package conformancetest is the shared `SkillStore` conformance harness.
|
Package conformancetest is the shared `SkillStore` conformance harness. |
|
drivers
|
|
|
localdb
Package localdb is Harbor's SQLite-backed `skills.SkillStore` driver.
|
Package localdb is Harbor's SQLite-backed `skills.SkillStore` driver. |
|
Package generator owns Harbor's in-runtime skill generator with persistence (RFC §6.7).
|
Package generator owns Harbor's in-runtime skill generator with persistence (RFC §6.7). |
|
Package importer ships Harbor's Skills.md importer — the predecessor's gap-closer (RFC §6.7).
|
Package importer ships Harbor's Skills.md importer — the predecessor's gap-closer (RFC §6.7). |
|
Package tools registers Harbor's planner-facing skill tools (`skill_search`, `skill_get`, `skill_list`) into the `tools.ToolCatalog`.
|
Package tools registers Harbor's planner-facing skill tools (`skill_search`, `skill_get`, `skill_list`) into the `tools.ToolCatalog`. |