Tool Catalogs & Schemas
Why Tool Catalogs Matter
Goa-AI agents generate a single, authoritative catalog of tools from your Goa designs. This catalog powers:
- planner tool advertisement (which tools the model can call),
- UI discovery (tool lists, categories, schemas),
- and external orchestrators (MCP, custom frontends) that need machine-readable specs.
You should never hand-maintain a parallel list of tools or ad-hoc JSON Schemas.
Generated Specs and tool_schemas.json
For each agent, Goa-AI emits a specs package and a JSON catalog:
Specs packages (
gen/<service>/agents/<agent>/specs/...)types.go– payload/result Go structs.codecs.go– JSON codecs (encode/decode typed payloads/results).specs.go–[]tools.ToolSpecentries with:- canonical tool ID (
tools.Ident), - payload/result schemas,
- hints (titles, descriptions, tags).
- canonical tool ID (
JSON catalog (
tool_schemas.json)Location:
gen/<service>/agents/<agent>/specs/tool_schemas.jsonContains one entry per tool with:
id– canonical tool ID ("<service>.<toolset>.<tool>"),service,toolset,title,description,tags,payload.schemaandresult.schema(JSON Schema).
Generated from the same DSL as the Go specs/codecs; if schema generation fails,
goa genfails so you never ship a drifted catalog.
This JSON file is ideal for:
- feeding schemas to LLM providers (tool/function calling),
- building UI forms/editors for tool payloads,
- and offline documentation tooling.
Runtime Introspection APIs
At runtime, you do not need to read tool_schemas.json from disk. The runtime
exposes an introspection API backed by the same specs:
agents := rt.ListAgents() // []agent.Ident
toolsets := rt.ListToolsets() // []string
spec, ok := rt.ToolSpec(toolID) // single ToolSpec
schemas, ok := rt.ToolSchema(toolID) // payload/result schemas
specs := rt.ToolSpecsForAgent(chat.AgentID) // []ToolSpec for one agent
Where toolID is a typed tools.Ident constant from a generated specs or
agenttools package.
This is the preferred way for:
- UIs to discover tools for a given agent,
- orchestrators to enumerate available tools and read their schemas,
- ops tooling to introspect tool metadata in a running system.
Choosing Between Specs and JSON
Use:
Specs & runtime introspection when:
- you are inside Go code (workers, services, admin tools),
- you want strong typing and direct access to codecs/schemas.
tool_schemas.jsonwhen:- you are outside Go (frontends, external orchestrators),
- you want a simple static JSON catalogue to load and cache.
Both are derived from the same design; choose whichever is more convenient for your consumer.
Typed Sidecars and Artifacts
Some tools need to return rich artifacts (for example, full time series, topology graphs, or large result sets) that are useful for UIs and audits but too heavy or detailed for model providers. Goa-AI models these as typed sidecars:
- The tool’s model-facing payload/result stay bounded and minimal.
- An optional sidecar type carries additional data alongside the tool result; it is never sent to the model, only stored/streamed for UIs and observers.
In the specs packages, each tools.ToolSpec entry includes:
Payload tools.TypeSpecResult tools.TypeSpecSidecar *tools.TypeSpec(optional)
tool_schemas.json mirrors this structure:
{
"tools": [
{
"id": "toolset.tool",
"service": "svc",
"toolset": "toolset",
"title": "Title",
"description": "Description",
"tags": ["tag"],
"payload": { "name": "PayloadType", "schema": { /* ... */ } },
"result": { "name": "ResultType", "schema": { /* ... */ } },
"sidecar": { "name": "SidecarType", "schema": { /* ... */ } }
}
]
}
Sidecar schemas describe the shape of planner.ToolResult.Sidecar for tools
that declare a sidecar type. Goa-AI also generates:
a generic
SidecarCodec(name string) (*tools.JSONCodec[any], bool)per toolset specs package, andper-tool helpers like:
func Get<GetTimeSeries>Sidecar(res *planner.ToolResult) (*GetTimeSeriesSidecar, error) func Set<GetTimeSeries>Sidecar(res *planner.ToolResult, sc *GetTimeSeriesSidecar) error
These helpers let executors attach typed sidecar artifacts to ToolResult
instances, while UIs and analytics code can decode them using the generated
schemas without guessing JSON shapes.
Agenttools and Typed Tool IDs
For exported toolsets (agent-as-tool), Goa-AI also generates an agenttools package under:
gen/<service>/agents/<agent>/agenttools/<toolset>/
These packages provide:
- typed tool ID constants (
tools.Ident), - alias payload/result types,
- codecs,
- and helper builders such as
New<Search>Call(...).
Example:
import (
chattools "example.com/assistant/gen/orchestrator/agents/chat/agenttools/search"
)
// Use a generated constant instead of ad-hoc strings
spec, _ := rt.ToolSpec(chattools.Search)
schemas, _ := rt.ToolSchema(chattools.Search)
// Build a typed tool call
req := chattools.NewSearchCall(&chattools.SearchPayload{
Query: "golang",
}, chattools.WithToolCallID("tc-1"))
Prefer these typed IDs and builders anywhere you reference tools (planners, orchestrators, UIs) to avoid stringly-typed bugs and keep your catalog aligned with the design.