moduletools

package
v2.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package moduletools adapts detached module capabilities into agent tools.

This is the seam the whole architecture exists for. A module declares capabilities; the host turns each enabled one into a tool the agent can call, so "assay my last three sessions" reaches sessions.assay without the agent knowing anything about the module that serves it, and without that module being linked into this binary.

The host stays in charge of everything that matters. It decides which capabilities become tools, supplies filesystem roots and subprocess binaries per invocation, enforces bounds, and validates what comes back. A module contributes a description and a schema; it does not gain authority by being installed.

Index

Constants

View Source
const ArtifactMarker = "@artifact "

ArtifactMarker prefixes the one-line JSON description of a produced artefact. The cockpit scans assistant output for it to render artefact cards; the model reads the same line as text, so both see identical facts.

Variables

View Source
var ReservedRequestKeys = map[string]bool{
	"protocol": true, "capability": true, "request_id": true,
	"input": true, "roots": true, "grants": true, "binaries": true,
	"deadline_ms": true, "max_output_bytes": true,
	"approval": true, "contract_version": true,
}

ReservedRequestKeys are request fields only the host writes. A caller's argument of one of these names never reaches the request root.

approval and contract_version are the host's in wire v2: an approval object a module read there would be taken as the host's own decision.

Functions

func ApprovalTool

func ApprovalTool(d *modproto.Descriptor, c modproto.Capability) approval.Tool

ApprovalTool is the policy's view of capability c of module d, as the agent sees it. The Modules page and module-invoke decide on it, so a capability is decided alike wherever it is called from.

func ArtifactRoots

func ArtifactRoots(d *modproto.Descriptor, home, workspace string) map[string]modproto.Root

ArtifactRoots are the roots a module's artefacts are read back from: the roots GrantRoots grants, without creating any directory, so a request that only reads never changes the disk. Source roots are left out: they are read-only, so no artefact is ever written into one.

func Disabled

func Disabled(dir string) bool

Disabled reports whether the module installed at dir has been turned off.

A module directory that cannot be read is treated as ENABLED: refusing to run a module because the host could not stat a marker would be a silent capability loss, and the failure to read is itself surfaced elsewhere.

func GrantRoots

func GrantRoots(d *modproto.Descriptor, home, workspace string, sources map[string]string) map[string]modproto.Root

GrantRoots maps the logical root NAMES a module declared onto real absolute paths, for one invocation.

This is where "installing a module grants nothing" is concrete: a module declares names, the host decides what each points at, and each module's own state is confined to its own directory so modules cannot see or corrupt each other's.

sources are the operator's named source roots (name -> absolute path), from the config and, for a command-line invocation, from --source-root. Only the names the module declared are consulted, each is granted READ-ONLY, and one that does not resolve is left out rather than replaced -- see ResolveSourceRoot and SourceRootWarnings. A nil map grants none.

func KnowledgeLoader

func KnowledgeLoader(installed []Installed) func(string) (string, string, []string)

KnowledgeLoader returns a function that composes one module's knowledge.

Discovery happens ONCE, here, because describing every installed module is a subprocess per module and a turn must not pay that. The returned closure is called per turn with whatever module the user selected.

It returns empty for an empty selection, which is the layered-composition rule made concrete: an installed module costs one line of capability summary until someone points the agent at it, and only then do its overlay and skills enter context.

Returns nil when nothing is installed, so a caller can tell "no modules" from "a module that contributes nothing".

func KnownModules

func KnownModules(installed []Installed) map[string]bool

KnownModules reports which module ids are installed, lowercased.

Exposed so a caller that must decide whether to ACT on a selection -- rather than merely load its knowledge -- can ask without repeating discovery.

func ModuleDir

func ModuleDir(home, binary string) (string, bool)

ModuleDir is the directory an installed module occupies.

It resolves from the BINARY rather than from the module id, because a module may be installed as <modules>/<id>/<binary> or as a bare binary directly in <modules>/ -- and a bare binary has no directory of its own to disable.

func ModulesDir

func ModulesDir(home string) string

ModulesDir is where installed modules live under the host state root. Only the host constructs this path; a module never needs to know it.

func New

func New(r *module.Runner, d *modproto.Descriptor, home, workspace string, sourceRoots map[string]string) []toolshared.Tool

New builds tools for every capability a module declares.

One tool per capability rather than one tool per module: the agent picks by what it wants to do, and each capability carries its own schema and its own declared effects, which is what makes per-capability approval possible.

func PlaceInput

func PlaceInput(req *modproto.Request, raw json.RawMessage, approved bool) error

PlaceInput puts a caller-supplied JSON object into a request.

A capability's RequestSchema describes Request.Input, but some modules read arguments beside Input at the request root, so an explicit "input" key is used verbatim and sibling keys pass through at the root. Host-owned fields are never overwritten, so a caller cannot widen roots, forge a request ID, or extend a deadline by naming one.

approved says whether the operator approved this call under the approval policy: a rule that allows it, or an answered ask ("Approve and run", --approve, or the owner's /approve). An approved call carries the approval claims in the arguments and records the host's own; any other call has them stripped. The agent's tool strips what the model wrote either way.

func ResolveSourceRoot

func ResolveSourceRoot(name, path string) (string, error)

ResolveSourceRoot checks one source root and returns the path to grant.

The shape rules are the config's own (a usable name, an absolute path), so a command-line root is held to exactly what a configured one is. On top of that the path must EXIST, and be a directory or a regular file: a root is handed over as-is and the module decides what to do with it, so a store kept as a single database file is as good as a directory of sessions. Anything else -- a socket, a device -- is refused rather than guessed at.

Nothing is ever substituted. A root that fails here is not granted, and the caller says why; the module then fails closed instead of reading something the operator did not point it at.

func SetDisabled

func SetDisabled(dir string, off bool) error

SetDisabled turns a module off or on.

Disabling does NOT uninstall: the binary, its state and its declared content stay exactly where they are, so re-enabling restores what the user had rather than re-fetching it.

func SourceRootNames

func SourceRootNames(d *modproto.Descriptor) []string

SourceRootNames returns the read roots a module declares that only the operator can supply, in declaration order and without duplicates.

func SourceRootWarnings

func SourceRootWarnings(d *modproto.Descriptor, sources map[string]string) []string

SourceRootWarnings reports every source root a module declares that the host will NOT grant from sources, and what the operator can do about it.

A warning rather than a refusal, like a missing bundle root: a module may not need every root for every capability, and refusing it outright for one it might not use would be worse than showing what is missing. What must not happen is silence -- a module whose data root was never granted describes itself fine, lists its capabilities fine, and fails only when invoked, for a reason nothing on screen explains.

func ToolName

func ToolName(moduleID, capabilityID string) string

ToolName is the agent-facing name for a capability.

Exported so the cockpit can show the same name the agent uses: a person reading "archive__sessions_assay" in a chat transcript should be able to find that capability on the Modules page.

Providers accept tool names of at most 64 letters, digits, '_' and '-', and reject the WHOLE request when one tool's name is anything else -- so one module's odd capability ID used to take down every tool call. Every other character becomes '_' ('-' too, as it always did, so existing names are unchanged), and a name past the limit is cut and given a digest suffix so it stays distinct. Names that still collide are refused at registration.

Types

type CapabilityTool

type CapabilityTool struct {
	// contains filtered or unexported fields
}

CapabilityTool exposes one module capability as an agent tool.

func (*CapabilityTool) ApprovalInfo

func (t *CapabilityTool) ApprovalInfo() approval.Info

ApprovalInfo tells the approval policy (tools.approval) what this tool is: a capability of its module, with the hints of the effects it declares. The policy decides each call before Execute runs.

func (*CapabilityTool) Description

func (t *CapabilityTool) Description() string

Description is what the agent reads when deciding whether to call this.

It states the declared effects in plain language, because the model choosing a cheap local capability over an expensive networked one is the first line of cost control -- long before the approval policy, which is the last.

func (*CapabilityTool) Execute

func (t *CapabilityTool) Execute(ctx context.Context, args map[string]any) *toolshared.ToolResult

Execute invokes the capability as a bounded detached process.

The approval policy decided this call before it got here: Execute runs what the policy let through, and refuses nothing on its own.

func (*CapabilityTool) Name

func (t *CapabilityTool) Name() string

Name is the tool name the model sees.

Capability IDs are already namespaced by module ("creative.tools.run", "sessions.assay"), but dots are not universally safe in tool names across providers, so they become underscores. The module ID is prefixed so two modules offering a similarly-named capability cannot collide.

func (*CapabilityTool) Parameters

func (t *CapabilityTool) Parameters() map[string]any

Parameters is the capability's declared request schema.

The module's own schema is handed to the model directly rather than being re-described by the host: the module owns its domain, and any translation here would be a second source of truth that could drift.

type Document

type Document struct {
	ModuleID string
	Version  string
	ID       string
	Title    string
	Path     string
	Digest   string
	Tokens   int
	Content  string
}

Document is one verified piece of module knowledge.

func (Document) Provenance

func (d Document) Provenance() string

Provenance is the attribution line recorded for anything entering agent context, so a later reader can tell where an instruction came from.

type HostConformance

type HostConformance struct {
	Module   string
	Findings []modprotov2.Finding
}

HostConformance is the host's verdict on ONE described module, and it is the thing a caller must consult before acting on any v2 declaration.

WHY A TYPE RATHER THAN AN ERROR. A conformance run produces per-target findings, and the operator ruling is that conformance is evaluated PER TARGET independently: a projection may not claim conformance while silently weakening a mandatory requirement, and one module's failure says nothing about another's. An error return would collapse that into a single boolean at the call site.

func CheckHostConformance

func CheckHostConformance(a modprotov2.Accepted) HostConformance

CheckHostConformance closes the triangle at the host:

product semantic declaration -> v2 projection -> host interpretation

THE HOST MUST FAIL MECHANICALLY IF THE PROJECTION WEAKENS AN ACCEPTED SEMANTIC. This is the one rule whose violation is invisible at the layer being reviewed: the Operation reads correct while the gate, which reads capability effects, fires on nothing. So the check runs here, and discovery refuses a module that fails it before registering any of its capabilities.

It takes an Accepted rather than a *Descriptor, which is not a stylistic choice: an Accepted can only be produced by modprotov2.Evaluate, so the contract pin has necessarily already run. The ordering the operator requires -- gate before reliance -- is therefore enforced by the type system rather than by this comment.

func (HostConformance) Conforms

func (h HostConformance) Conforms() bool

Conforms reports whether the host may act on this module's v2 declarations.

func (HostConformance) Refusal

func (h HostConformance) Refusal() string

Refusal renders the verdict for a person, with reason AND remedy.

Every finding is listed rather than only the first. A validator that reports one problem at a time turns a single bad descriptor into as many rebuild cycles as it has defects, and the author cannot see whether they are unrelated or one cause.

type Installed

type Installed struct {
	Runner     *module.Runner
	Descriptor *modproto.Descriptor
	Warnings   []string
	// HostWarnings are the HOST's findings about this module, kept apart from
	// what the module said about itself.
	//
	// They were merged, which made a stale self-diagnostic from a module and a
	// digest mismatch found by the host look identical on the Modules page --
	// and one of those the operator can act on. The invoke path had already
	// drawn this line; discovery had not.
	HostWarnings []string
	// V2 is the module's behavioural-contract standing: the pin outcome, and
	// the host's no-weakening verdict when the pin passed.
	//
	// Always populated, including for v1 modules and refusals. A nil-means-v1
	// field would make three outcomes share one observable, and telling them
	// apart is the entire job of the pin.
	V2 V2
	// Disabled is set when the user has turned this module off. It is still
	// DISCOVERED -- the Modules page must show it, with its capabilities, so
	// there is something to turn back on -- but it contributes no tools.
	Disabled bool
	// Err is set when the module could not describe itself. A broken module is
	// reported rather than fatal: one bad install must not hide the others.
	Err error
}

Installed is one discovered module.

func Discover

func Discover(ctx context.Context, home string) []Installed

Discover finds installed modules and asks each to describe itself.

Installation is a file on disk plus a successful describe. There is no registry file to drift out of sync with the executable: the binary is the source of truth about what it can do.

Modules are described a few at a time, and an unchanged binary is answered from module.DescribeCached: this runs on every Modules or Tools page load and every cockpit invocation, and describing each module in turn with a 10s budget each made one slow module stall the page.

func RegisterTools

func RegisterTools(ctx context.Context, home, workspace string, register func(toolshared.Tool)) ([]string, []Installed)

RegisterTools turns every capability of every installed module into an agent tool, and returns a one-line summary per module for the agent's context.

This is where "modules contribute tools" becomes real. The agent gains the ability to call a capability without knowing what a module is; the host keeps every decision about authority. The discovered set is returned alongside the summaries so a caller can compose module knowledge later without describing every module a second time -- each describe is a subprocess, and a per-turn rediscovery would make selecting a module cost more than using it.

The operator's source roots are read here, once per registration, because the agent's composition root hands the module host nothing but its state root. A config reload re-registers the tools, so a changed root is picked up the same way every other tool setting is.

type Knowledge

type Knowledge struct {
	// Overlays are module-authored instruction documents, already verified.
	Overlays []Document
	// Skills are progressively loaded module knowledge, already verified.
	Skills []Document
	// Warnings record content that was declared but could not be used. They are
	// surfaced rather than swallowed: a silently missing overlay means the agent
	// behaves differently with no visible reason.
	Warnings []string
}

Knowledge is the module content composed into one turn.

func LoadKnowledge

func LoadKnowledge(installed []Installed, selectedModules, requestedSkills []string) Knowledge

LoadKnowledge composes the selected module's overlay and skills.

selectedModules is the user's explicit selection -- the connector gesture. When it is empty nothing is loaded, which is the point: an installed module costs one line of capability summary until someone asks for it.

requestedSkills narrows further. When a capability is in play the host passes the skill IDs that capability declares, so a turn loads only the knowledge that turn needs.

func (Knowledge) Compose

func (k Knowledge) Compose() (overlays string, skills string)

Compose renders loaded knowledge as prompt sections.

Each document carries its provenance inline, so a reader of the assembled prompt can tell which module supplied which instruction and verify it.

func (Knowledge) Tokens

func (k Knowledge) Tokens() int

Tokens is the declared context cost of everything loaded, so the host can budget before composing rather than discovering the cost afterwards.

type KnowledgeTool

type KnowledgeTool struct {
	// contains filtered or unexported fields
}

KnowledgeTool exposes the existing digest-verified loader as an ordinary, read-only tool. A module selection is not required.

func (*KnowledgeTool) Description

func (t *KnowledgeTool) Description() string

func (*KnowledgeTool) Execute

func (t *KnowledgeTool) Execute(_ context.Context, args map[string]any) *toolshared.ToolResult

func (*KnowledgeTool) Name

func (t *KnowledgeTool) Name() string

func (*KnowledgeTool) Parameters

func (t *KnowledgeTool) Parameters() map[string]any

type V2

type V2 struct {
	// Decision carries the pin outcome. Its Pin.Reason and Pin.Remedy explain a
	// refusal to a person.
	Decision modprotov2.Decision

	// Conformance is the host's no-weakening verdict, and is only populated
	// when the pin returned v2. There is nothing to check on a v1 module: its
	// descriptor has no Operation layer to compare a capability against.
	Conformance *HostConformance

	// Err is set when the descriptor could not be evaluated at all, which is
	// a different failure from a refused contract and is reported as itself.
	Err error
}

V2 is a discovered module's v2 standing: which contract governs it, and whether it conforms.

Present on every Installed, including v1 modules and refusals, because the alternative -- a nil field meaning "v1, probably" -- makes three outcomes share one observable. A caller must be able to tell "no v2 declaration" from "declared something this host refuses" from "not evaluated at all".

Jump to

Keyboard shortcuts

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