Tools let models trigger deterministic side effects—API calls, computations, retrieval queries, or even other agents—while respecting schema validation and observability. Everything lives under tool/
and works with the shared core.Tool
contract, while tool/retrieval
adds convenience helpers for search-style capabilities.
Examples assume am := github.com/hupe1980/agentmesh
and core := github.com/hupe1980/agentmesh/core
.
Function tools
tool.NewFuncTool
and tool.NewFuncToolFromType
wrap pure Go functions with JSON Schema validation. They are perfect when you want quick utility functions or lightweight integrations.
- When to use: simple RPC-like operations, deterministic calculations, thin wrappers over internal services.
- Behavior:
- Validates incoming JSON arguments against your schema before calling the function
- Normalizes errors to
tool.Error
codes (VALIDATION_ERROR
,EXECUTION_ERROR
) - Runs safely in parallel; no shared mutable state
type SumArgs struct {
A float64 `json:"a"`
B float64 `json:"b"`
}
sumTool, _ := tool.NewFuncToolFromType("calculate_sum", "Add two numbers", SumArgs{}, func(ctx context.Context, tc core.ToolContext, args SumArgs) (any, error) {
return args.A + args.B, nil
})
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.Tools = []core.Tool{sumTool}
})
Prefer handwritten schemas? NewFuncTool
accepts a map[string]any
schema instead of deriving it from a struct.
Toolsets
Registering dozens of tools up front can overwhelm the prompt. Implement core.Toolset
to load tools on demand based on the current context, or reuse the built-in adapters (for example, MCP).
- When to use: dynamic connectors, per-user tool catalogs, or rate-limited APIs.
- Behavior:
- Toolsets decide at call time which tools to expose via
ListTools
- Works alongside inline tools; the executor merges duplicates by name
- Often paired with caching or feature flags to keep prompts trim
- Toolsets decide at call time which tools to expose via
type DocsToolset struct{}
func (DocsToolset) ListTools(ctx context.Context, ro core.ReadonlyContext) ([]core.Tool, error) {
// e.g., only surface tools relevant to the active workspace
return []core.Tool{sumTool, searchDocsTool}, nil
}
researcher, _ := am.NewModelAgent("researcher", llm, func(o *am.ModelAgentOptions) {
o.Toolsets = []core.Toolset{DocsToolset{}}
})
MCP toolset
The tool/mcp
adapter lets you connect to external MCP servers and expose their declared tools to your agents. It handles session pooling, schema conversion, and remote execution over stdio or HTTP transports.
- When to use: integrate hosted tool providers, share capabilities with other MCP-compliant runtimes, or proxy heavy operations out of process.
- Behavior:
- Discovers remote tools at runtime via
ListTools
- Reuses pooled sessions keyed by auth headers for efficiency
- Supports stdio (
command
), streamable HTTP, and SSE transports out of the box
- Discovers remote tools at runtime via
import mcptool "github.com/hupe1980/agentmesh/tool/mcp"
factory := mcptool.NewStdioSessionFactory("mcp-server", []string{"serve"})
mcpToolset := mcptool.NewToolset(factory, func(o *mcptool.ToolsetOptions) {
o.NamePrefix = "remote"
})
defer mcpToolset.Close()
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.Toolsets = append(o.Toolsets, mcpToolset)
})
Need to authenticate over HTTP instead? Swap in mcptool.NewStreamableSessionFactory
or mcptool.NewSSESessionFactory
with custom headers. The adapter forwards ToolContext
metadata so nested tool calls can still access artifacts and plugins.
AgentTool
tool.NewAgentTool
turns an existing agent into a tool, allowing higher-level planners to delegate entire flows. It spins up a nested runner with isolated artifacts and state.
- When to use: hierarchical planners, reusable sub-agents, fallback escalation paths.
- Behavior:
- Shares the caller’s plugin manager and artifact store via
ToolContext
- Streams events back into the parent run; final text becomes the tool response
- Works with any
core.Agent
(model-based or purely functional)
- Shares the caller’s plugin manager and artifact store via
summarizer := am.NewSequentialAgent("summarizer", []core.Agent{writer, editor})
summarizerTool := tool.NewAgentTool(summarizer)
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.Tools = append(o.Tools, summarizerTool)
})
LangChainGo tool
The tool/langchaingo
adapter wraps any langchaingo
tools.Tool
so it can be used as an AgentMesh core.Tool
without rewriting integrations. Try it with the built-in calculator from github.com/tmc/langchaingo/tools
—the same one showcased in examples/langchaingo
.
- When to use: reuse existing LangChainGo tool implementations alongside native AgentMesh tools.
- Behavior:
- Mirrors name and description from the wrapped tool by default (override via options)
- Presents a single string argument (
__arg1
) that is forwarded to the LangChainGo tool - Surfaces validation errors using
tool.Error
for consistent error handling
import (
langchainTool "github.com/hupe1980/agentmesh/tool/langchaingo"
lctools "github.com/tmc/langchaingo/tools"
)
calcTool := langchainTool.NewTool(&lctools.Calculator{})
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.Tools = append(o.Tools, calcTool)
})
Need additional metadata or custom validation? Pass option functions to NewTool
to override the generated name and description or wrap the result with your own schema enforcement.
Retrieval tools
The tool/retrieval
helpers make it easy to expose search connectors as strongly typed tools and to compose multiple retrievers together.
Wrap retrievers as tools
retrieval.NewTool
converts any retrieval.Retriever
into a regular core.Tool
that accepts a query
string. Returned documents use the shared retrieval.Document
shape (PageContent
, Score
, Metadata
) so downstream agents receive consistent payloads.
retriever := retrieval.NewMergerRetriever([]retrieval.Retriever{bedrock, kendra})
searchTool := retrieval.NewTool(
"knowledge_base_search",
"Search the enterprise knowledge sources and return the top documents.",
retriever,
)
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.Tools = append(o.Tools, searchTool)
})
Merger retriever
retrieval.NewMergerRetriever
fans out to multiple retrievers and merges their document lists. Use option functions to tune behavior:
WithMergerMaxParallel(n)
bounds concurrent requests (default is4
; pass0
to force sequential execution).WithMergerStopOnFirstError(true)
cancels remaining calls after the first failure (default istrue
); otherwise errors are aggregated viaerrors.Join
and successful documents are still returned.
retriever := retrieval.NewMergerRetriever(
[]retrieval.Retriever{bedrock, kendra, langchain},
retrieval.WithMergerMaxParallel(2),
retrieval.WithMergerStopOnFirstError(false),
)
Documents preserve the order of the input retriever slice, and duplicate metadata is left untouched so you can attribute results to the right source.
Built-in connectors
AgentMesh ships ready-to-use retrievers that plug straight into the wrapper above:
tool/retrieval/amazonbedrock
– call Amazon Bedrock Agent Runtime knowledge bases and translate their scores intoretrieval.Document
objects.tool/retrieval/amazonkendra
– query Amazon Kendra indexes with optional attribute filters and user context.tool/retrieval/langchaingo
– adapt any LangChainGo retriever or vector store into the AgentMesh interface.
Each package uses the same Options
pattern (func(*Options)
) for advanced tuning and includes unit tests demonstrating expected behavior. Mix and match them with MergerRetriever
to build hybrid search stacks.
Tool execution
Under the hood, agents rely on tool.NewParallelToolExecutor(maxParallel)
to execute function calls. It enforces concurrency limits, records metrics, emits trace spans, and protects against panics.
- Max concurrency defaults to the batch size; configure it to bound resource usage.
- Tool runs gain a
core.ToolContext
exposing session state, artifact helpers, and plugin hooks. - Errors are aggregated so the agent can decide whether to retry, escalate, or continue.
selector := flow.NewDefaultSelector(&flow.Executors{
AgentExecutor: agent.DefaultAgentExecutor,
ModelExecutor: model.DefaultModelExecutor,
ToolExecutor: tool.NewParallelToolExecutor(4),
})
planner, _ := am.NewModelAgent("planner", llm, func(o *am.ModelAgentOptions) {
o.FlowSelector = selector
o.Tools = []core.Tool{sumTool, summarizerTool}
})
Combine these building blocks to give agents actionable capabilities without sacrificing determinism or observability.