@gonk/tool-orchestrator
v0.3.1
Published
Composes ToolRegistry instances into a principal-aware catalog with discovery, ranking, introspection, and pin lifecycle.
Readme
@gonk/tool-orchestrator
Composes one or more ToolRegistry instances into a single catalog with discovery, ranking, and pin lifecycle. Auto-registers meta-tools the model can use to find and load on-demand tools.
What it does
- Composes registries — multiple sources unioned into one dispatch surface.
- Search & recommend — keyword/tag-based ranker by default, fully pluggable.
- Per-adapter visibility — reads
hints.{mcp,pi}.visibilityto overridedef.visibilityper scope. - Pin lifecycle —
pin()andunpin()queue;commitPins()flushes at adapter-chosen boundaries (turn boundary / compaction). Append-only with tombstones for prompt-cache stability. - Meta-tools auto-registered (visibility:
always):list_tools— list all known tools, filterable by category/tag/visibilityfind_tools— keyword search across name, description, tags, keywordsget_tool— full schema + metadata for a specific toolload_tool— pin a tool for the rest of the sessionunload_tool— unpin
When ToolContext.auth is present, all six discovery/introspection meta-tools
authorize tool.discover against canonical tool names before returning names,
descriptions, schemas, rankings, or changing pins. Targeting a hidden tool has
the same result as targeting a missing tool and does not mutate pin state.
Trusted no-auth local invocation preserves the legacy full-catalog behavior.
Search & ranking
find_tools uses BM25 with weighted fields: name (3×), category (2×), description/tags/keywords (1×). Constants: k1=1.2, b=0.75. The default ranker (bm25Search) is exported and pluggable — pass a custom search function to createOrchestrator to replace it. legacySubstringSearch is exported as a fallback for callers that need simple substring matching.
Active set semantics
activeSet() = { always tools, sorted by name } ∪ { committed pins, in pin order }Deterministic, byte-stable between commits — meant to be safe to put in the prompt prefix without busting cache.
Entry points
import { createOrchestrator } from "@gonk/tool-orchestrator";
import type { Orchestrator } from "@gonk/tool-orchestrator/types";
import { metaTools } from "@gonk/tool-orchestrator/meta-tools";Example
import { ToolRegistry } from "@gonk/tool-registry";
import { createOrchestrator } from "@gonk/tool-orchestrator";
const r = new ToolRegistry();
r.register([/* ...your tools... */]);
const orch = createOrchestrator({
registries: [r],
scope: "pi", // resolves per-adapter visibility hints
pinStore: { load, save }, // optional persistence — Pi: session-scoped; MCP: ephemeral
});
orch.search("audio"); // → ranked tools
orch.activeSet(); // → tools to expose to the model right now
orch.pin("watch-todos");
await orch.commitPins(); // narrow→broad active-set rebuild