builtin

package
v1.24.0 Latest Latest
Warning

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

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

Documentation

Overview

Package builtin ships the small set of opt-in tools that travel with the Harbor binary. Built-ins exist to give a freshly-scaffolded agent a zero-dependency way to prove the planner → executor → trajectory loop without forcing an operator to author Go code or attach an MCP server first. V1.1 ships two built-ins:

  • `clock.now` — returns the current UTC time as RFC 3339 + epoch milliseconds. Useful as a heartbeat / sanity-check tool.
  • `text.echo` — returns its `text` input verbatim. Useful as a smoke-test action the planner can call without side effects.

Built-ins are NEVER registered implicitly. The operator opts in via the `tools.built_in` yaml field, which the assembly fan-out (`internal/runtime/assemble`) passes to `builtin.RegisterWith`. An empty list registers nothing — the registry is purely additive and opt-in by design.

canonical skills surface. The `skill_search` / `skill_get` / `skill_list` / `skill_propose` built-ins are thin delegations to the `internal/skills/tools` handlers and the `internal/skills/generator`. The capability envelope (which tools the run may see) is computed per call from the catalog's visible set under the run's identity + granted scopes — never LLM-supplied — so the capability filter, tool-name redaction, and the `skill_get` token budgeter run on the production path.

The §4.4 seam pattern applies in the same shape as OAuth drivers (`internal/tools/auth/drivers/oauth2`) and planner drivers (`internal/planner/react`): the `internal/config` validator mirrors `KnownNames()` so a typo in the yaml fails at validation time rather than at boot. A drift test (`builtin_test.go`) asserts the two surfaces stay in lockstep.

Concurrent reuse. Built-in tools are registered through `inproc.RegisterFunc`, which captures the closure into a fresh `ToolDescriptor` per call. The functions themselves (`clock.Now`, `text.Echo`) hold no per-invocation state and are safe for concurrent use; the concurrent-reuse contract is trivially satisfied through the existing inproc driver's contract.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnknownBuiltIn is returned when a name in `tools.built_in`
	// is not in the registered set. The wrapped message lists every
	// known name so an operator sees the typo immediately.
	ErrUnknownBuiltIn = errors.New("builtin: unknown built-in tool")
	// ErrRegisterFailed wraps an underlying `inproc.RegisterFunc`
	// failure. Should be impossible at runtime (the inproc deriver
	// has unit tests against all built-in payload types) but is
	// surfaced loudly per §13 fail-loud posture.
	ErrRegisterFailed = errors.New("builtin: failed to register built-in tool")
)

Sentinel errors. Callers (`cmd/harbor/cmd_dev.go::bootDevStack`, the devstack mirror, the config validator) compare via errors.Is.

View Source
var ErrDeclarativeActionMissingTool = errors.New("declarative_action: envelope missing `tool` discriminator")

ErrDeclarativeActionMissingTool is the sentinel a declarative_action invocation returns when the operator-supplied envelope omits the `tool` discriminator. The LLM sees the structured error observation and corrects on the next turn.

View Source
var ErrDeclarativeActionReservedName = errors.New("declarative_action: planner-reserved tool name (use the native call shape instead)")

ErrDeclarativeActionReservedName is the sentinel a declarative_action invocation returns when the LLM tried to use the escape-hatch tool to invoke a planner-reserved name (`_finish` / `_spawn_task` / `_await_task`). These names are NOT in the catalog — they are planner-level primitives the projector handles natively. Emitting them through `declarative_action` is a model-side mistake; the planner surfaces the error observation + escalates `FinishRepair` (for `_finish`) so the next turn's prompt nudges the LLM toward the right channel.

View Source
var ErrIdentityRequired = errors.New("builtin: identity (tenant/user/session) is mandatory")

ErrIdentityRequired is the sentinel meta-tools surface when ctx arrives without a complete (tenant, user, session) triple. Returned instead of silently zero-quadruple-falling-through to a SkillStore call that would either error or — worse — leak across tenants (CLAUDE.md §6 rule 9 + §13).

Functions

func KnownNames

func KnownNames() []string

KnownNames returns the sorted list of built-in tool names the binary ships with. The `internal/config` validator's `allowedBuiltInTools` allowlist mirrors this slice; the `TestKnownNames_MirrorsConfigAllowlist` test enforces no drift.

func Register deprecated

func Register(cat tools.ToolCatalog, names []string) error

Register attaches each named built-in to the catalog. Equivalent to RegisterWith(ctx, names) with a zero RegistryContext — use when no builtins need the skill store.

Deprecated: prefer RegisterWith for new call sites. Kept for backward compatibility with existing tests + devstack wiring.

func RegisterWith added in v1.2.0

func RegisterWith(rc RegistryContext, names []string) error

RegisterWith attaches each named built-in to the catalog, passing the full RegistryContext so builtins that need the SkillStore (skill_search, skill_get) can reach it. Builtins that don't use the store ignore it.

Types

type ArtifactFetchArgs added in v1.2.0

type ArtifactFetchArgs struct {
	// Ref is the artifact identifier surfaced by a prior tool result's
	// fetch footer.
	Ref string `json:"ref"`
	// MaxBytes bounds the returned window. Zero or negative applies the
	// deployment's configured default; a value above its configured
	// ceiling is SERVED AT THE CEILING and reported through the same
	// Truncated / TotalSizeBytes / ReturnedBytes fields as any other
	// bound — never refused, never silently shortened.
	MaxBytes int `json:"max_bytes,omitempty"`
	// Offset is the zero-based BYTE index the returned window starts at.
	// It is how a model pages a large artifact: read at 0, then re-call
	// with Offset set to the previous Offset + ReturnedBytes while
	// Truncated is true.
	//
	// The window is a byte range and is MIME-agnostic — it knows nothing
	// about lines, rows or records, so a window may begin and end
	// mid-line and the caller splits the text itself. That is deliberate:
	// a stored MIME is not revisable on a content-addressed store, so
	// keying read behaviour on one would turn a wrong stamp into a
	// permanent refusal.
	//
	// An offset at or beyond the artifact's size is not an error — the
	// content is empty and Truncated is false, because nothing follows
	// the window. A negative offset is a soft error rather than a guess.
	Offset int `json:"offset,omitempty"`
}

ArtifactFetchArgs is the typed input shape (the inproc deriver generates a JSON Schema the LLM sees from these tags).

type ArtifactFetchOut added in v1.2.0

type ArtifactFetchOut struct {
	Ref       string `json:"ref"`
	MIME      string `json:"mime,omitempty"`
	SizeBytes int64  `json:"size_bytes"`
	// Content is the admitted window as text. It is exactly
	// `blob[Offset : Offset+ReturnedBytes]` — the requested range trimmed
	// to whole runes at both ends — so a model paging by the documented
	// rule reassembles the artifact with no gaps and no duplicates.
	//
	// A window that is not valid UTF-8 after that trimming is REFUSED
	// rather than delivered: `Content` is empty and `Error` names the
	// stored MIME, the failing byte offset and the by-reference route
	// those bytes can travel. The refusal exists because this field is a
	// string on its way through `encoding/json`, which rewrites every
	// invalid byte to U+FFFD — so a binary artifact would arrive
	// corrupted, at a different length than `ReturnedBytes` reported.
	Content string `json:"content,omitempty"`
	// Offset is the byte index the returned window ACTUALLY starts at.
	// When the requested offset landed mid-rune it is the requested
	// offset advanced past the continuation bytes that were trimmed, not
	// the requested offset echoed back — a paging model computes its next
	// call as `Offset + ReturnedBytes`, and echoing a stale offset would
	// drop the trimmed bytes with no signal.
	Offset int64 `json:"offset"`
	// ReturnedBytes is the length of `Content` in bytes.
	ReturnedBytes int64 `json:"returned_bytes"`
	// TotalSizeBytes is the artifact's full stored size — the
	// denominator a bounded read is bounded against. It duplicates
	// SizeBytes, which predates this field and is kept because shipped
	// prompts and transcripts name it; both are the same number.
	TotalSizeBytes int64 `json:"total_size_bytes"`
	// Truncated reports whether bytes remain AFTER the returned window
	// (`offset + returned_bytes < total_size_bytes`). It is true
	// whichever bound applied — the caller's `max_bytes`, the
	// deployment's default, or the deployment's ceiling — so the model
	// never has to know WHICH bound applied to know that one did. Re-call
	// with `offset` advanced to read on.
	Truncated bool `json:"truncated,omitempty"`
	// Error carries soft failures. When set, `Content` is empty and
	// the LLM sees the error message as the observation text.
	Error string `json:"error,omitempty"`
}

ArtifactFetchOut is the typed return shape. `Error` is a soft-error channel (e.g. unknown ref, cross-tenant rejection) the LLM reads as observation text without the runtime aborting the run — same shape as the discovery meta-tools.

type ClockNowArgs

type ClockNowArgs struct{}

ClockNowArgs is the input shape for `clock.now`. The tool takes no arguments — the empty struct keeps the schema deriver (`internal/tools/schema.Derive`) happy without forcing the planner to fabricate a payload.

type ClockNowOut

type ClockNowOut struct {
	RFC3339  string `json:"rfc3339"`
	EpochMS  int64  `json:"epoch_ms"`
	Timezone string `json:"timezone"`
}

ClockNowOut is the result shape for `clock.now`. Both representations are returned so callers that need a string format (logging, prompt injection) and callers that need integer math (deduplication windows, freshness checks) get the value in the shape they want without re-parsing.

func ClockNow

func ClockNow(_ context.Context, _ ClockNowArgs) (ClockNowOut, error)

ClockNow returns the current UTC time. Pure / read-only — no dependency on identity, no side effect, safe for concurrent invocation.

type DeclarativeActionArgs added in v1.2.0

type DeclarativeActionArgs struct {
	// Tool is the catalog name to dispatch. Reserved names (`_finish`,
	// `_spawn_task`, `_await_task`) return ErrDeclarativeActionReservedName
	// — they are planner-level, not catalog entries.
	Tool string `json:"tool,omitempty"`
	// Args is the JSON arguments to pass to Tool. Validated against
	// the tool's args schema before dispatch; an invalid shape returns
	// a structured `repair_outcome.args_repaired=true` observation so
	// the next planner step's prompt escalates ArgsRepair guidance.
	Args json.RawMessage `json:"args,omitempty"`
	// Body is the alternate salvage input: a raw JSON envelope (or
	// array of envelopes) that the `repair.ActionParser` parses. A
	// multi-action array trips MultiAction; a parse failure trips
	// ArgsRepair.
	Body json.RawMessage `json:"body,omitempty"`
}

DeclarativeActionArgs is the meta-tool's input envelope. Two canonical shapes the LLM can emit:

  • **Typed**: `{tool: "<catalog name>", args: {...}}`. Direct dispatch — the most common case.
  • **Salvage**: `{body: "<raw text>"}`. The body is fed through `repair.ActionParser`, which tolerates fenced JSON / prose- wrapped JSON / multi-action arrays. Used by LLMs whose instruction-following produces messier output shapes.

When both are supplied, `Tool`/`Args` win. When neither is supplied, the meta-tool returns `ErrDeclarativeActionMissingTool`.

type DeclarativeActionOut added in v1.2.0

type DeclarativeActionOut struct {
	// Dispatched is true when the inner tool's Invoke returned without
	// error. The planner / LLM treat this as a successful dispatch.
	Dispatched bool `json:"dispatched"`
	// Tool is the resolved inner-tool name (echoed for observability —
	// the trajectory step's Action carries declarative_action, not the
	// inner name, so this field surfaces the actual call target).
	Tool string `json:"tool,omitempty"`
	// Observation is the inner tool's typed result, JSON-encoded. The
	// LLM consumes this as the next turn's tool-result content.
	Observation json.RawMessage `json:"observation,omitempty"`
	// Error is the human-readable error message when Dispatched=false.
	Error string `json:"error,omitempty"`
	// RepairOutcome carries the across-step repair classification the
	// React planner reads on the next step.
	// Nil means "no repair signal" (a clean dispatch resets counters
	// the same way a clean native step does).
	RepairOutcome *DeclarativeRepairOutcome `json:"repair_outcome,omitempty"`
}

DeclarativeActionOut is the meta-tool's structured observation shape. The planner walks the trajectory at the start of its next step (see `internal/planner/react/declarative_outcomes.go`) and reads `RepairOutcome` to drive the per-run RepairCounters — closing the across-step repair-escalation loop that the native main path no longer touches.

type DeclarativeRepairOutcome added in v1.2.0

type DeclarativeRepairOutcome struct {
	// ArgsRepaired is true when the inner tool's args failed schema
	// validation OR when the salvage parser could not extract an
	// envelope. Drives planner.RepairCounters.ArgsRepair.
	ArgsRepaired bool `json:"args_repaired,omitempty"`
	// MultiAction is true when the salvage parser returned more than
	// one envelope in a single body. Drives
	// planner.RepairCounters.MultiAction.
	MultiAction bool `json:"multi_action,omitempty"`
	// FinishRepair is true when the LLM tried to invoke a planner-
	// reserved finish marker (`_finish`) through declarative_action.
	// Drives planner.RepairCounters.FinishRepair so the next turn's
	// prompt nudges the LLM toward issuing a content-only terminal.
	FinishRepair bool `json:"finish_repair,omitempty"`
}

DeclarativeRepairOutcome maps onto the per-run `planner.RepairCounters`. The React planner reads it at the start of the step that follows a declarative_action dispatch and bumps the matching counter; on a clean step it stays nil so the planner resets all three counters per the existing semantics.

type RegistryContext added in v1.2.0

type RegistryContext struct {
	Catalog       tools.ToolCatalog
	SkillStore    skills.SkillStore
	ArtifactStore artifacts.ArtifactStore
	Bus           events.EventBus
	Redactor      audit.Redactor
	GrantedScopes []string
	// ArtifactFetchDefaultMaxBytes and ArtifactFetchHardMaxBytes are the
	// operator's artifact read-back bound: the window `artifact_fetch`
	// serves when the model names none, and the ceiling the model's own
	// bound is clamped to. Optional — a non-positive value takes the
	// built-in default, which is what keeps a catalog assembled without
	// a configuration working.
	//
	// They are the SAME operator policy the Protocol's byte read serves
	// under, so the tool and the wire surface cannot drift into two
	// different ceilings.
	ArtifactFetchDefaultMaxBytes int
	ArtifactFetchHardMaxBytes    int
}

RegistryContext carries the dependencies builtins may need at registration time. Fields are optional for builtins that don't use them. Two failure postures, both fail-loud:

  • Store-shaped deps (`SkillStore`, `ArtifactStore`) fail at INVOKE time with an operator-readable message when nil — the registration is structurally valid, the backing subsystem is simply not configured.
  • Wiring-shaped deps (`Bus` for every skill_* delegation; `Redactor` additionally for `skill_propose`) fail at REGISTRATION time — a missing bus/redactor is a boot-path bug, not an operator configuration choice.

`GrantedScopes` is the operator-declared `tools.granted_scopes` list. The skill_* delegations derive the run's capability envelope from `tools.VisibleNames(Catalog, ...)` under these scopes — default-deny: an empty list means tools with AuthScopes are invisible and skills requiring them are filtered.

type SkillGetArgs added in v1.2.0

type SkillGetArgs struct {
	// Names are the skill names to fetch. Missing names are skipped
	// (a partial response beats a hard error for stale model state).
	Names []string `json:"names"`
	// MaxTokens caps the combined estimated token count of the
	// returned skills via the tiered budgeter ladder. 0 → the
	// handler default (1024).
	MaxTokens int `json:"max_tokens,omitempty"`
}

SkillGetArgs is the LLM-facing input shape for the `skill_get` built-in. The pre-111d single-`name` shape is replaced by the skills-tools handler's multi-name + budget envelope — the `capability` field is omitted and server-computed (see skill_capability.go).

type SkillListArgs added in v1.3.0

type SkillListArgs struct {
	Scope    skills.Scope `json:"scope,omitempty"`
	TaskType string       `json:"task_type,omitempty"`
	Tags     []string     `json:"tags,omitempty"`
	Limit    int          `json:"limit,omitempty"`
	Offset   int          `json:"offset,omitempty"`
}

SkillListArgs is the LLM-facing input shape for the `skill_list` built-in (— the skills-tools third tool's first production registration). The `capability` field is omitted and server-computed (see skill_capability.go).

type SkillSearchArgs added in v1.2.0

type SkillSearchArgs struct {
	Query string `json:"query"`
	Limit int    `json:"limit,omitempty"`
}

SkillSearchArgs is the LLM-facing input shape for the `skill_search` built-in. The shape deliberately OMITS the rich handler's `capability` field — the envelope is server-computed from the run's visible-tool set (see skill_capability.go) so a model cannot self-grant a wider skill view. The pre-111d `tags` filter is dropped; tag-scoped enumeration is `skill_list`'s job.

type TextEchoArgs

type TextEchoArgs struct {
	Text string `json:"text"`
	Tag  string `json:"tag,omitempty"`
}

TextEchoArgs is the input shape for `text.echo`. Both fields are emitted in the schema; `text` is the payload, `tag` is an optional caller-supplied label that round-trips alongside the echo so a planner that fans out several echo calls in parallel can tell them apart on the return.

type TextEchoOut

type TextEchoOut struct {
	Echoed string `json:"echoed"`
	Tag    string `json:"tag,omitempty"`
}

TextEchoOut is the result shape for `text.echo`.

func TextEcho

func TextEcho(_ context.Context, in TextEchoArgs) (TextEchoOut, error)

TextEcho returns the input text verbatim. Useful for smoke-testing the planner → executor → trajectory loop without an external dependency, and as a deterministic stand-in when authoring an agent before its real tools are wired.

type ToolGetArgs added in v1.2.0

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

type ToolGetOut added in v1.2.0

type ToolGetOut struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	ArgsSchema  string `json:"args_schema,omitempty"`
	Found       bool   `json:"found"`
	Error       string `json:"error,omitempty"`
}

type ToolSearchArgs added in v1.2.0

type ToolSearchArgs struct {
	Query string   `json:"query"`
	Tags  []string `json:"tags,omitempty"`
	Limit int      `json:"limit,omitempty"`
}

type ToolSearchOut added in v1.2.0

type ToolSearchOut struct {
	Tools []ToolSearchResult `json:"tools"`
	Count int                `json:"count"`
}

type ToolSearchResult added in v1.2.0

type ToolSearchResult struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Tags        []string `json:"tags"`
}

Jump to

Keyboard shortcuts

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