Adding a tool
A walkthrough of how to extend the catalog with a new tool. Covers both paths: a provider module under src/lib/tools/providers/ for any new tool, and the legacy native catalog at src/lib/chat-tools.ts when the tool needs to share helpers with the existing surface. Includes naming, ToolContext use, schema shape, status labels, connection gates, testing, and the rules of thumb that keep new tools usable for the model.
Overview
- Preferred path
- Provider module
- Module folder
src/lib/tools/providers/- Module type
ToolModuleinsrc/lib/tools/types.ts- Native catalog
src/lib/chat-tools.ts- Skill allow-list
- Add the new tool name to skill manifests in
src/lib/skills/registry.ts - Activity feed
- Status label drives the live pill in the sidebar
- Typecheck
npx tsc --noEmit -p tsconfig.json
Adding a tool is the most common kind of change once the engine is stable. The contract is small, the typecheck catches the common mistakes, and the new tool lights up in the catalog on the next chat handler restart. The hard parts are not in the code; they are in the schema description, the connection gate, and deciding which skills should be allowed to call the tool at all.
Decision tree
Module or native. Pick once.
| Situation | Path |
|---|---|
| New provider, no existing tools for it | Provider module |
| New tool for an existing provider that already lives in a module | Add to the existing module file |
| New tool that depends on shared helpers in chat-tools.ts (web search, scraping, log_activity) | Native catalog |
| Quick fix to an existing native tool | Native catalog |
| Tool intended to be deprecated within the next quarter | Native catalog |
Naming conventions
A few small rules that keep the catalog scannable.
| Rule | Why |
|---|---|
| Lowercase, underscore separated | Matches every existing native tool. Easy to grep. |
| Prefix with the provider id where the tool wraps an external service (stripe_, brevo_, gmail_) | Lets the activity feed cluster tools by provider |
| Use a verb in the tool name (create, list, send, search) | The model picks better when the name reads like a sentence stem |
| Keep the name under 40 characters | Long names truncate in the activity sidebar |
| Never collide with an existing name | Dispatcher routes by name; collisions are silent |
Add a tool as a provider module
The end-to-end recipe for a new module.
- 01Create the fileAdd
src/lib/tools/providers/<provider>.ts. Start from an existing module likestripe.tsas a structural reference. - 02Define the schemas arrayEach entry is a tool-call function definition. Keep the description short and operational; one or two sentences max.
- 03Write the executors mapOne async function per tool name. Signature is
(ctx: ToolContext, args) => Promise<string>. Return a string the model can read. - 04Add status labels (optional)Map each tool name to a synchronous function that returns a short verb phrase rendered in the activity sidebar.
- 05Export the moduleDefault export an object that implements
ToolModule:{ provider, schemas, executors, statusLabels }. - 06Register itOpen
src/lib/tools/registry.tsand add the module to theMODULESarray. That is the only place outside the module that needs to change. - 07Update skill allow-listsOpen
src/lib/skills/registry.tsand add the new tool name to thetoolsarray of any skill that should be allowed to call it. Add the same names to the SKILL.md frontmatter for consistency. - 08Typecheck and pushRun
npx tsc --noEmit -p tsconfig.json. Commit, push, deploy.
Add a tool to the native catalog
When the new tool needs to share helpers in chat-tools.ts.
- 01Pick a sectionFind the area of
chat-tools.tsthat matches the new tool's category. Schemas are grouped by category. - 02Add the schemaAppend the tool-call schema to the section's schemas array. Keep the description short and operational.
- 03Add the executorFind
_executeTool(or the equivalent dispatch function for the section) and add a case for the new name that calls a private function defined elsewhere in the file. - 04Add a status labelIf the tool deserves a custom label, add a branch to
getToolStatusLabel. Otherwise the default label runs. - 05Update the connection gate (if applicable)Add the tool name to the provider map in
toolPassesConnectionGateso users without the provider connected do not see the tool. - 06Update skill allow-listsSame as the module path. Open
src/lib/skills/registry.tsand add the tool name to skills that should call it. - 07Typecheck and pushRun the typecheck. Confirm no other tool name changed. Ship.
ToolContext reference
Every executor receives this object. Knowing what is on it saves duplicated lookups.
ctx.logActivity and ctx.saveMemory are convenience wrappers around the system tools of the same name. They write to the same tables but do not consume a turn. Use them when a tool wants to surface progress or persist a finding without going through the model.Writing the schema
A schema description is the only thing the model reads when deciding to call a tool. Treat it like prompt engineering.
| Do | Avoid |
|---|---|
| Lead with a verb. Lists, creates, sends, returns. | Past tense. Was used to do X. |
| Name the upstream provider explicitly when relevant. | Generic descriptions that could apply to multiple tools. |
| Mention the operation cost in token count if non-trivial. | Bragging about features the model cannot see. |
| Document required arguments inline in the description if their meaning is non-obvious. | Repeating the JSON-Schema property descriptions in prose. |
| Use the exact name of the related skill or persona when scoping the tool. | Inventing aliases the rest of the catalog does not use. |
Writing the executor
An executor takes ctx and args, does the work, returns a string.
The return value is what the model sees. Return JSON when the model needs to read structured fields. Return a short summary string when the model only needs the gist. Return an error string starting with Error: when the call fails for a reason the model should react to (provider not connected, rate limit, missing input). The platform turns thrown exceptions into a generic tool error; an explicit error string gives the model a useful payload to work with.
Status labels
A short verb phrase rendered while the tool is in flight.
Connection gates
Gates remove the tool from the catalog when the upstream provider is not connected.
A new tool that wraps an external provider almost always needs a gate entry. Without one, the tool serializes to the model even when the user has not connected the provider, which leads to confident attempts that fail at the first call. The user-friendly outcome is for the tool to vanish from the catalog until the integration is connected.
Exposing the tool to skills
A tool is invisible to skills until they list it in their allow-list.
Testing
A handful of checks before the tool reaches a real user.
| Check | How |
|---|---|
| Typecheck | npx tsc --noEmit -p tsconfig.json |
| Schema validity | Hit the chat handler with a debug flag and inspect the serialized tool catalog. The new schema should appear with the right name and description. |
| Connection gate | Disconnect the provider on a test account, open a session, confirm the tool does not appear in the catalog. |
| Happy path | Connect the provider, prompt a skill that lists the tool, watch the activity sidebar for the status label and the eventual completion. |
| Error path | Force the upstream to fail (revoke the token, point at a bad URL) and confirm the model receives a usable error string. |
| Status label | Verify the label appears under the right tool name and is under 60 characters. |
Shipping
The deploy is the same as any other change to the chat surface.
The build pipeline runs the typecheck, the linter, and the bundle step, then ships a new container on every release. The new tool becomes available on the next chat handler invocation; sessions in progress at the moment of deploy do not see the change until their next turn.
Gotchas
The mistakes that show up in code review.
registry.ts. The typecheck does not catch the omission.File map
Glossary
- Provider module
- A self-contained tool bundle under src/lib/tools/providers/. Preferred path for new tools.
- Native catalog
- The historical surface in src/lib/chat-tools.ts. Still receives changes when a tool needs to share helpers.
- Tool name
- The string the model uses to call a tool. Lowercase, underscore separated, unique across the merged catalog.
- Schema description
- The short operational sentence on a schema. Treated as prompt by the discovery and selection steps.
- Executor
- The async function that runs when the model calls a tool. Receives ToolContext plus args, returns a string.
- Connection gate
- The runtime filter that removes a tool from the catalog when its provider is not connected for the user.
- Status label
- A short verb phrase rendered in the activity sidebar while the tool is running. Synchronous, never throws.
- Skill allow-list
- The tools array on a SkillMeta. A tool not listed here is not exposed to that skill at runtime.