Skip to content

Tools and transports

A planner should reason about what it can do, not how the doing is wired. Harbor makes that literal: there is exactly one Tool concept, and the catalog hides whether a given tool is local Go code, an HTTP endpoint, an MCP server, a remote A2A agent, or an internal Flow. The planner emits a decision to call a tool; the Runtime figures out the rest.

This page distills RFC §6.4. For the authoritative design, defer to the RFC. To build something, jump to add an in-process tool or the define-a-tool recipe.

What a Tool is

A Tool is a typed, self-describing capability. It carries everything the Runtime and the planner need to call it safely and the LLM needs to choose it well:

FacetWhat it isWhy it matters
SchemasTyped input and output shapesThe planner is given the input schema; the Runtime validates calls against it
Side-effect classA declared class — pure, read, write, external, or statefulLets governance and HITL gates reason about consequences
Auth scopesThe credentials a call requiresDrives tool-side OAuth and the pause/resume primitive when a scope is missing
HintsAdvisory cost / latency / safety metadata for selectionHelps the planner pick the right tool without trial and error
ToolPolicyTimeout, retries, backoff, validate modeA resilience contract enforced by the Runtime, not hand-rolled per tool

The ToolPolicy is the resilience seam: a flaky HTTP endpoint and a fast local function are the same concept with different timeout and retry settings. You declare the policy; the Runtime applies it on every invocation. When ToolPolicy is left zero-valued, sensible defaults fire — so the common case ("register this function") is production-resilient with zero ceremony.

The catalog and TransportKind

The ToolCatalog registers, resolves, and lists tools. Behind a single TransportKind it hides where the work actually happens:

TransportKindBacked byReach for it when
InProcessGo code in your binaryYour first tools and anything that is just a function
HTTPA JSON-over-HTTP endpointWrapping an existing internal or third-party API
MCPA Model Context Protocol serverConsuming the growing MCP tool ecosystem
A2AA remote agent over Agent-to-AgentDelegating to another agent as a callable capability
FlowAn internal Harbor subflowComposing a multi-step playbook as one callable tool

To the planner, all five are identical: a name, an input schema, and a result. Swapping a tool from an in-process prototype to a hardened HTTP service is a catalog change, not a planner change.

Dispatch is the Runtime's job, not the LLM provider's

This is the load-bearing distinction. In many stacks, "tools" are a feature of the LLM provider's API — the model returns a provider-specific tool-call shape and your code adapts to whatever each vendor emits. Harbor inverts that.

The planner emits a CallTool decision (one arm of the Decision sum type returned by Next). The Runtime — not the LLM provider — invokes the tool through the dispatcher, applies the ToolPolicy, validates against the schema, captures provenance, and routes the result. Provider differences disappear because the LLM client carries no tool surface at all: its single Complete method has no Tools or ToolChoice field.

Why this matters

Because dispatch lives in the Runtime, every transport, every provider, and every planner share one execution path — with consistent retries, timeouts, identity capture, and artifact handling. There is no per-provider tool adapter to drift. See Runtime vs. Planner for the seam this rests on.

Tools and identity

Tools run inside the mandatory identity triple (tenant, user, session), but they never handle raw scopes. Identity flows through context.Context, and a tool reads what it needs from there. The Runtime captures the full triple in the call's provenance regardless of what the tool does.

Artifact and scoped-storage access goes through a per-task scoped facade that auto-stamps the identity triple on writes and scope-checks every read. A tool cannot reach across sessions or tenants by accident — the facade closes that path by construction, and a missing identity component fails closed rather than degrading silently. The isolation model is covered in full under Identity and isolation.

Tools never see raw scopes

A tool that tries to read or pass an identity scope directly is working against the grain. Read identity from ctx; reach storage through the injected scoped facade. The facade is the contract, not a convenience.

Heavy results route to the ArtifactStore

A tool that returns a 4 MB report must not stuff those bytes into the planner's reasoning context. Harbor enforces this: heavy outputs route through the ArtifactStore, and the planner sees an ArtifactRef (a content-addressed handle), not the bytes.

  • The heavy-output threshold defaults to 32 KB, is runtime-configurable, and is per-tool overridable.
  • There is no opt-in flag and no NoOp fallback — offloading is the behavior, not a setting.
  • A runtime-wide safety net guarantees no raw heavy content ever reaches the LLM: a single enforcement pass at the LLM-client edge fails loudly with ErrContextLeak rather than quietly truncating.

The planner can then decide to fetch, summarize, or hand the reference to another tool — all by reference. The full mechanism, including attachment disposition policy, lives under Artifacts and context safety.

Which transport should I pick?

Start in-process. Your first tools are almost always plain Go functions — fastest to write, easiest to test, no network. Register them in the catalog as InProcess.

Reach for an external transport when the capability already lives outside your binary:

  • HTTP — you have, or want, a JSON API boundary. Good for tools owned by another team or service.
  • MCP — you want to consume an existing MCP server from the ecosystem instead of reimplementing it.
  • A2A — the capability is itself an agent, and delegating to it (rather than copying its logic) is the right factoring.
  • Flow — the "tool" is really a multi-step Harbor subflow you want to expose under one name.

Because the planner sees one Tool concept, you can prototype InProcess and promote to HTTP/MCP/A2A later without touching planner logic — only the catalog registration changes.

Build it

  • Add an in-process tool — the operator playbook for registering your first Go-backed tool end to end.
  • Define a tool — the recipe: schemas, side-effect class, ToolPolicy, and catalog registration in code.
  • Artifacts and context safety — where heavy results go and how the context safety net enforces it.
  • RFC §6.4 — the authoritative design for the tool catalog and transports.

Apache-2.0 licensed — see LICENSE.