Overview
Give a Mastra agent the ability to research the live web. Ask a question in one sentence. Get back a written answer with sources, per-claim citations, and a confidence grade. The@nimble-way/mastra package brings Nimble Web Search Agent research into Mastra as three typed tools. Nimble plans the research, searches, and opens pages. It reads JavaScript-heavy sites, cross-checks what it finds, and returns a cited answer. Your agent sends a task and collects the result.
That research takes minutes, not milliseconds. A high effort run takes 5 to 15 minutes, far longer than a single agent turn. So by default none of the three tools block: the run keeps going server-side and the runId is the only handle needed to collect it. Bounded waiting is opt-in, and the wait option is the one way to make the result tool block.
- A cited answer from the live open web: prose or JSON, with sources, per-claim citations with verbatim excerpts, and confidence grades, read at run time.
- Gets cheaper the more you run it: a reused Web Search Agent remembers which sources worked. Recurring research runs up to 50% cheaper.
- Start now, collect later: start the run in one turn, read the result in another turn, another process, or another day.
- The model never sees your credential: the API key is resolved server-side and sent only as a request header.
Which path to take
Two ways to get Nimble research into a Mastra agent. Pick one in ten seconds.Nimble MCP Server
This package
Prerequisites
Node.js 22.13 or later
Node.js 22.13 or later
engines.node >= 22.13.0.@mastra/core 1.56.0 or later, and zod
@mastra/core 1.56.0 or later, and zod
@mastra/core ^1.56.0 and zod ^3.25.1 || ^4.0.0.The zod floor is 3.25.1 rather than 3.25.0 for a reason. 3.25.0 declares a ./v4 export whose target is missing from its published tarball, and Mastra’s schema layer resolves that path, so the combination fails at import. The declared range excludes it, so npm refuses the install rather than letting it fail at run time.Verified against the published 0.1.1: zod 3.25.1, 3.25.76, and 4.0.0 all work with @mastra/core 1.56.0, as does 4.5.4 with 1.64.0.NIMBLE_API_KEY (server-side only)
NIMBLE_API_KEY (server-side only)
A model provider credential
A model provider credential
model at run time, so the provider needs its own key in the environment. The examples here use anthropic/claude-sonnet-4-6, which reads ANTHROPIC_API_KEY.This is separate from NIMBLE_API_KEY. Nimble does the research; your model decides when to ask for it.A Web Search Agent ID
A Web Search Agent ID
wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9.A reused agent keeps its memory, so it gets faster, more accurate, and up to 50% cheaper on recurring work. Create one in the dashboard or with POST /v2/agents.The agent is developer configuration, not model input. The model can never choose which agent to run.Quick Start
Install
0.1.1 is the current release. It is published from CI over OIDC and carries a SLSA provenance attestation, so the artifact on npm can be traced to the commit and workflow that built it.Set the environment
Attach the tools to an agent
createNimbleAgentTools() returns all three tools sharing one credential and one agent configuration. Pass { agentId, apiKey } to override the environment.Ask it something
nimble-agent-start-run and answers in seconds with a task_run_… ID, which it reports in result.text. The research keeps going server-side for minutes. Collect it later with the same ID.Example Response
A completed run, as the result tool returns it. This is the prose shape. An agent with a stored output schema returns"type": "json" with a json field instead of text, and its claims carry path rather than callout:
How it works
The model starts a run
nimble-agent-start-run sends POST /v2/agents/{agent_id}/runs and returns immediately with a real task_run_… ID. The turn is never blocked for the run’s duration.The run proceeds server-side
Anything with the runId can check it
nimble-agent-run-status returns an instant snapshot. nimble-agent-run-result returns the answer, or { ready: false } while the run is still working.The answer arrives with its evidence
trust: sources, per-claim citations with verbatim excerpts, and confidence grades.The three tools
nimbleAgentStartRun, nimbleAgentRunStatus, and nimbleAgentRunResult.
Those three inputs are the complete model-facing surface. The credential, the agent, the effort ceiling, the output schema, and the source boundary are all developer configuration.
The resumable lifecycle
TherunId is the only handle. Any process configured with the same agent can check or collect a run it did not start.
Parameters
agentId
agentId
wsa_0a1b2c3d4e5f60718293a4b5c6d7e8f9. Defaults to process.env.NIMBLE_AGENT_ID. Resolved at execute time, so the model can never choose the agent.Missing it raises NimbleConfigError: Missing Nimble agent id.apiKey
apiKey
process.env.NIMBLE_API_KEY. Read server-side at execute time and sent only as a request header.Missing it raises NimbleConfigError: Missing Nimble API key.effort and effortCap
effort and effortCap
effort is the tier used when the model does not choose one. Leave it unset and the agent instance’s own configured default applies, which is the recommendation.effortCap is the ceiling on what the model may request. It defaults to high, so a model cannot unilaterally reach the x-high and max cost tiers. A developer-set effort is not clamped.See effort tiers for what each tier does and costs.Verified behaviour, reading the request body the package sends:max is a custom-budget tier that is coming soon, so the API may reject it even though the package forwards it. Ask for x-high if you want the top available tier now.outputSchema
outputSchema
output_schema. Use it when the run must return structured data.Keep this in developer configuration. Do not ask the model to author JSON, source URLs, or excerpts in its prompt.output.type: "json" with output.json instead of output.text.sources
sources
sources.allow, a group with a blank title, a group with an empty domains array, and a blank domain string.wait
wait
timeoutMs: 300_000 and pollIntervalMs: 10_000, with a 100 ms floor. Runs take minutes, so polling faster only spends requests. Waiting respects Mastra’s per-call AbortSignal.When the timeout elapses the tool returns { ready: false } and the run keeps going server-side. A bounded wait never invents a terminal status: it returns the last authoritative one. If the initial status request itself reaches the deadline, status is unknown and activity is omitted, because no lifecycle snapshot was received.clientOptions
clientOptions
baseURL, fetch, timeout, and maxRetries.maxRetries applies to status and result reads only. Run creation is always sent with maxRetries: 0, whatever this value is.Per-tool configuration
Individual factories are exported when the three tools need different settings:A long-running research example
This is the shape the package is built for. The run outlives the turn that started it.runId wherever your application keeps state. It is the only handle needed, and it stays valid across processes and restarts.
createNimbleClient(apiKey) and call nimble.agents.runs.create, .get, and .result directly.Citations and trust metadata
Every completed run carries atrust object:
confidenceandreasoning: the run’s own grade of its answer, and why.sources: every source used, withtypeandsource_category.claims: per-claim confidence, pluscitationscarrying theurland the verbatim excerpts the claim rests on. In a prose answer, acalloutnumber ties the claim to its marker in the text. Under an output schema there is no prose, so claims carry apathinstead: the JSON path of the value they support. Read whichever is present, notcalloutalone.
trust is passed through verbatim. snake_case field names are preserved and unknown future fields are kept, so nothing is silently dropped and prose citation markers stay aligned with the text.
Handling the API key
NIMBLE_API_KEY is a server-side credential. The package resolves it at execute time and sends it only as a request header.
Verified against the published package, reading the requests it actually sends: the key appears in Authorization: Bearer … and nowhere else. It is absent from all three tool input schemas, absent from tool outputs, and scrubbed from error messages.
If you inject your own client with client, attribution headers and credential handling become your concern. Pass the matching apiKey too, or server error details are withheld, because the package cannot prove they are safe to expose.
Failure and not-ready behaviour
An active run is never an error. Two cases matter, and the package treats them differently on purpose.A run that is still working
A run that is still working
{ ready: false } with the run’s status and isActive. Not an error. Check again later.Branch on ready, never on status. A not-ready payload can carry any status, including a terminal one, and it never carries output. The same HTTP 409 on a completed run reports differently depending on wait:if (status === "completed") render(output.text) throws on a payload that is entirely legal. A status reported after a wait deadline is also the last snapshot taken rather than a fresh read, so a run that failed after that snapshot can still report running. Confirm with the status tool, which never waits, before treating a run as alive.A run that terminally failed or was cancelled
A run that terminally failed or was cancelled
NimbleAgentRunError with reason: "failed", the server’s message, and the runId preserved so the run can still be inspected or reported.Verified message shape: Nimble agent run task_run_… failed: <server message>.Run creation failed, definitely
Run creation failed, definitely
createOutcome: "not-created" and reason: "request". No run exists. Retry only after the underlying condition clears.Run creation failed, ambiguously
Run creation failed, ambiguously
createOutcome: "unknown". The run may have been created server-side.An ambiguous create is never retried automatically. Run creation is billed and not idempotent, so the POST is sent once with maxRetries: 0. The transient-retry policy is disabled for creation only. Status and result reads keep normal retries.Reconcile before creating again: list recent runs for the agent, or check the dashboard. An automatic retry here bills a second run.The thrown error carries no runId, because none was returned, so a run that was created cannot be reconciled by ID. Reconcile by agent and timestamp instead.maxRetries: 0 only removes the transport retry. A model that is told to retry a failed start reinstates it one layer up, and the package cannot tell that apart from a new task. Keep the “do not try again” clause in the example instructions.An out-of-contract payload
An out-of-contract payload
reason: "protocol" on NimbleAgentRunError, not a TypeError.Mastra background tasks
Mastra can run a tool call as a background task, so the agent turn returns before the tool finishes. This is optional. By default the Nimble tools already return immediately. Reach for it when you want Mastra to hold the thread open and re-invoke the agent once the answer lands. Enable it on the Mastra instance, then opt the tool in:untilIdle, the stream stays open until the background task completes and the agent is re-invoked with the result:
Nimble research or Mastra’s built-in web search
Mastra ships its ownwebSearchTool. It is not an alternative to Nimble research, and the difference is worth ten seconds of thought.
webSearchTool resolves the active model at runtime and delegates to that provider’s own search. When the provider has none, it throws a MastraError with the id WEB_SEARCH_UNSUPPORTED_PROVIDER.
Reach for webSearchTool for a quick in-turn lookup on a supported provider. Reach for a Nimble run when the answer has to be researched rather than recalled, has to carry its evidence, or has to be reproducible across models. They coexist: attach both and let the instructions route between them.
Using the Nimble MCP Server instead
If you want Nimble tools in a Mastra agent without adding a dependency, connect the Nimble MCP Server over Streamable HTTP:max, the server-enforced output schema and source allowlist, and the explicit not-created against unknown distinction on a failed create.
Choose the MCP server for an interactive agent that needs breadth. Choose the package when research is the job and you need those controls.
The MCP server page covers the full transport options, the authentication options including OAuth, and the complete tool table. This section does not repeat them.
Attribution
Every request the package makes carriesX-Client-Source: mastra. The package sets this itself, on run creation and on every status and result read. There is nothing to configure, and nothing for you to set.
The value is a constant. It carries no credential and no user-identifying data.
The one exception is an injected client. The package then makes no requests of its own, so attribution travels only if your client sets the header.