Manage tools as code

Keep your Tool definitions in your repository. Review and version them with your code, then converge them at deploy time. This page applies the ensure protocol to saved Tools. For related guides, see Manage Agents as code and Manage Flows as code.

Define the tool

defineTool is a local constructor that does not make network requests. It validates the top-level definition shape and returns a canonical definition object. The definition includes the tool’s name, description, toolType, parametersSchema, and config fields.

Use the following code to define a weather lookup Tool:

TypeScript SDK
1import { defineTool } from '@runtypelabs/sdk'
2
3export const weatherLookup = defineTool({
4 name: 'Weather Lookup',
5 description: 'Fetch the current weather for a city',
6 toolType: 'external',
7 parametersSchema: {
8 type: 'object',
9 properties: { city: { type: 'string' } },
10 required: ['city'],
11 },
12 config: { url: 'https://example.com/weather?q={{city}}', method: 'GET' },
13})

Use one of these toolType values: external, custom, graphql, mcp, local, flow, or subagent. A Tool’s identity consists of its name and account scope. The account scope is organizational or personal. The API endpoint and credentials determine the environment.

If you rename a definition, ensure creates a Tool with that name and leaves the old Tool in place. ensure never deletes or renames Tools. Treat the name as the stable identity.

flow and subagent Tools can reference saved Flows and Agents through config.flowId and config.agentId. Raw flow_ and agent_ identifiers are tied to an account and environment. Use references such as flow:Order Lookup and agent:Billing Specialist for portable definitions.

Converge at deploy time

Configure the SDK and converge the Tool definition with the following code:

TypeScript SDK
1import { Runtype } from '@runtypelabs/sdk'
2
3Runtype.configure({ apiKey: process.env.RUNTYPE_API_KEY })
4
5const result = await Runtype.tools.ensure(weatherLookup)

The result includes result, toolId, and contentHash. The result value is unchanged, created, or updated.

ensure starts with a hash-only probe. In the managed steady state, the SDK sends one probe and writes no content changes. When the definition differs, the SDK sends the full definition and creates or updates the Tool unless the conflict rules reject the write.

The content hash covers toolType, description, parametersSchema, and config. The name identifies the Tool and does not contribute to the hash. When a response includes contentHash, the server computes it from the canonical, normalized definition. The SDK uses that server-computed hash in later probes.

Tools do not have version snapshots. ensure has no release: 'publish' option or versionId result. It converges the live Tool definition directly.

Detect drift in CI

A dry run sends the full definition without writing changes and returns a plan. Run a dry run with the following code:

TypeScript SDK
1const plan = await Runtype.tools.ensure(weatherLookup, { dryRun: true })

The plan includes result: 'plan', a changes value of none, create, or update, changedKeys, contentHash, and an optional remoteHash. The changedKeys array names changes such as toolType, description, parametersSchema, and changed keys within config.

To make a CI drift check fail when the plan is not none, pass expectNoChanges: true:

TypeScript SDK
1await Runtype.tools.ensure(weatherLookup, { expectNoChanges: true })

The SDK throws ToolDriftError when the plan reports create or update.

To bind an apply step to the state that the dry run inspected, pass its remoteHash as expectedRemoteHash:

TypeScript SDK
1await Runtype.tools.ensure(weatherLookup, { expectedRemoteHash: plan.remoteHash })

If the remote state changes before the apply step, the API returns a 409 conflict with code: 'remote_changed'.

Conflicts with dashboard edits

The API records the source of each Tool write. When a dashboard or API edit changes a managed Tool away from its code definition, the next ensure returns status code 409 by default. The SDK exposes this response as a ToolEnsureConflictError with code: 'external_modification'. The dashboard shows a Managed in code badge and warns you before you save an edit.

Choose one of these resolutions:

  • Repository wins: Pass onConflict: 'overwrite' to overwrite the dashboard edit with the repository definition.
  • Dashboard wins: Call pull, review the returned definition as a git diff, then commit or revert the change.

Pull the canonical definition with the following code:

TypeScript SDK
1const { definition, contentHash, lastModifiedSource, updatedAt } =
2 await Runtype.tools.pull('Weather Lookup')

What ensure does and does not manage

ensure converges the Tool definition, including its name, description, type, parameters schema, and config. It applies the same custom-tool code validation and secret-reference validation as Tool creation and updates. For example, use a {{secret:API_KEY}} reference when API_KEY is the secret name.

ensure does not manage which Agents or Flow steps reference the Tool. It never deletes or renames Tools.

Call the API directly

If you do not use the TypeScript SDK, authenticate each request with Authorization: Bearer YOUR_API_KEY. Replace YOUR_API_KEY with your Runtype API key. The ensure protocol has three steps:

  1. Send a hash-only request to POST /v1/tools/ensure with name and contentHash: { "name": "Weather Lookup", "contentHash": "YOUR_SHA256_HASH" }. Replace YOUR_SHA256_HASH with the lowercase hexadecimal SHA-256 hash for the definition. A matching hash returns status code 200 with { "result": "unchanged" }. A nonmatching hash returns status code 200 with { "result": "definitionRequired" }.
  2. When the response is { "result": "definitionRequired" }, resend the request with the full definition and omit contentHash. The server recomputes the canonical hash over the submitted definition and returns contentHash on content-bearing responses. Use that value in later probes.
  3. Handle conflicts as follows. The API returns status code 409 for external_modification or remote_changed. If the submitted contentHash does not match the server’s recomputed hash for the definition, the API returns status code 422 with content_hash_mismatch. Omit contentHash from full requests to avoid this validation error.

To pull a Tool definition directly, send GET /v1/tools/pull with the name query parameter. The API returns the canonical definition and contentHash, lastModifiedSource, and updatedAt fields.

Next steps

Continue with these guides: