Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.useterse.ai/llms.txt

Use this file to discover all available pages before exploring further.

Most AI workflows have a problem: every action runs through the model. Need to update a CRM record? The model decides if, when, and how. Need to send a Slack message? Same thing. This works when you need judgment, but it’s wasteful and unreliable when you already know exactly what to do. Deterministic tool calls let you call integration tools directly from your workflow code. No model reasoning, no token spend, no chance the LLM decides to skip the step or call the wrong tool. The backend still handles credentials and OAuth. You just skip the AI middleman.

When to use each approach

DeterministicAgentic
How it worksYour code calls a specific tool with exact parametersThe model chooses which tools to call based on a prompt
API surfacetoolbox.*generateText({ prompt, skills })
When to useYou know the tool, parameters, and order in advanceYou need the model to reason, summarize, or decide
PredictabilityAlways runs the same wayOutput varies across runs
Token costZero LLM tokensTokens consumed per run

agent.tools.*

The generated SDK attaches typed wrappers under agent.tools.* for every integration declared in the agent’s skills. Use them when you want a deterministic call inside an agentic handler.
import { TerseAgent, createJob } from "terse-sdk"

import { AttioObject, Skills, SlackChannel, Triggers } from "./terse.generated"

createJob({
    name: "new-deal-alert",
    triggers: [Triggers.attio.onRecordCreated({ object: AttioObject.Deal })],
    onTrigger: async (event) => {
        const agent = TerseAgent.create({
            prompt: "You alert the deal desk about new deals with quick context.",
            skills: [Skills.attio({ object: AttioObject.Deal }), Skills.slack({ channel: SlackChannel.DealDesk })]
        })

        // Deterministic: post to Slack directly, no LLM in the loop
        await agent.tools.slack.sendMessage({
            channelId: SlackChannel.DealDesk.channelId,
            message: `New deal: ${event.record.values.company_name}`
        })
    }
})
Every method on agent.tools.* is generated by terse generate based on your connected integrations. The wrappers are fully typed, so your editor autocompletes tool names, parameter shapes, and return types. Note: agent.tools.* is scoped to integrations declared in the agent’s skills. If you need unfiltered access from code, use toolbox instead.

toolbox.*

Code generation exports a toolbox object alongside agent.tools.*. It exposes the same typed integration methods, but you do not need a TerseAgent, and it is not filtered by skills. Use it when you only want deterministic tool calls.
import { toolbox, SlackChannel } from "./terse.generated"

await toolbox.slack.sendMessage({
    channelId: SlackChannel.DealDesk.channelId,
    message: "Shipped without constructing TerseAgent."
})

Mixing deterministic and agentic calls

The real power is combining both in a single workflow. Use deterministic calls for predictable operations and hand off to the model when you need reasoning.
import { generateText } from "terse-sdk"
import { z } from "zod"
import { toolbox } from "./terse.generated"

createJob({
    name: "deal-enrichment-and-scoring",
    triggers: [Triggers.attio.onRecordCreated({ object: AttioObject.Deal })],
    onTrigger: async (event) => {
        // 1. Agentic: let the model research the company and reason about fit
        const parsed = await generateText({
            prompt: [
                "Research this company and score it for ICP fit (1-100).",
                `Company: ${event.record.values.company_name}`,
                `Domain: ${event.record.values.company_domain}`
            ].join("\n"),
            skills: [Skills.attio({ object: AttioObject.Deal }), Skills.web()],
            outputSchema: z.object({ score: z.number(), rationale: z.string() })
        })

        // 2. Deterministic: write the result back to the CRM
        await toolbox.attio.upsertRecord({
            object: AttioObject.Deal,
            matchingAttribute: "record_id",
            records: [{
                record_id: event.record.id,
                fit_score: parsed.score,
                scoring_notes: parsed.rationale
            }]
        })

        // 3. Deterministic: notify the team
        await toolbox.slack.sendMessage({
            channelId: SlackChannel.DealDesk.channelId,
            message: `Scored ${event.record.values.company_name}: ${parsed.score}/100 - ${parsed.rationale}`
        })
    }
})
Steps 2 and 3 always execute the same way. Step 1 is where the model adds value: researching and reasoning about data that would be hard to codify as rules.

Available deterministic tools

Every integration you connect generates deterministic wrappers. Run terse generate to see what’s available in your project. Common examples:
IntegrationExample tools
AttioqueryRecords, upsertRecord, listObjects
SlacksendMessage, listChannels, listUsers, readConversation
GitHubsearchCode, grepCode, readFile, listPullRequests, listCommits
LinearcreateTicket, updateTicket, searchTicket, addComment
SnowflakeexecuteQuery
GmailsendEmail, createDraft
Re-run terse generate when your connected integrations change. Do not hand-edit src/terse.generated.ts.

Where to go next

Context as Code

How terse generate creates typed helpers from your workspace.

TypeScript SDK reference

Full reference for toolbox.*, agent.tools.*, and more.