builtin

package
v1.26.4 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 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 skill reads or writes can reach their narrowed dependency. Builtins that don't use skills 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
	// SkillReader optionally supplies a read-only skill projection for
	// skill_search, skill_get, and skill_list. When nil those readers retain
	// source compatibility by falling back to SkillStore. Mutating built-ins
	// such as skill_propose always require SkillStore.
	SkillReader   skills.SkillReader
	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 (`SkillReader`/`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