Skip to main content
TerseAgent is the runtime that reasons and acts on your behalf: an agentic loop with tool-calling, structured outputs validated with zod, and a strict allowlist that prevents the model from touching anything you didn’t explicitly grant. Most of the time you don’t construct it yourself. generateText is the shorthand you reach for in almost every job — give it a prompt and the skills the model may use, and it runs the agentic loop to completion and returns the result. The model can call any tool granted via skills while it works.
generateText is a thin wrapper over TerseAgent. You only construct the agent directly — TerseAgent.create() — for the two things generateText doesn’t expose: streaming partial output with run(), or reusing one agent instance across several calls. The rest of this page describes that underlying agent and the allowlist that governs both paths. For fixed, deterministic actions, skip the agent entirely and call toolbox.*.

Agentic loop

Every run executes a full agentic loop: the model picks a tool, the SDK executes it, the result is fed back, and the loop continues until the model produces a final answer. Every step is recorded in Activity so you can replay the conversation later. The model only sees the tools you grant through skills. Everything else is invisible to it: your environment variables, your other integrations, and your other workflows.

Structured outputs

Pass an outputSchema (a zod schema) to generateText and the SDK forwards it to the model, parses the final JSON, and validates it before returning. The result is fully typed. (On a TerseAgent, the same schema goes as the second argument to runAndWait.)
If the model returns malformed JSON or fails schema validation, the run errors out, Terse marks it Failed in Activity, and no half-applied side effects reach your CRM.

Strict ACL on skills and resources

The most important property of TerseAgent is what it won’t do. The Terse runtime enforces a strict allowlist on every model-driven tool call:
  • The model can only see the integrations declared in skills. If Slack isn’t in skills, the model has no way to discover or call any Slack tool, even if your workspace has Slack connected.
  • Skill configuration further narrows the surface. Skills.attio({ object: AttioObject.Deal }) means the model can read and write records on the Deal object only, not your other Attio objects or workspaces.
  • Skills.slack({ channel: SlackChannel.DealDesk }) pins messaging to one channel. The model can’t @here your #general.
  • Skills.github({ repos: [...] }) restricts code access to specific repositories.
  • Tools listed in toolApprovals are paused for human approval before execution.
Tool calls outside this allowlist are rejected before any external API is touched. skills and toolApprovals work the same whether you pass them to generateText or TerseAgent.create(). For unfiltered deterministic access from code, use the toolbox export from ./terse.generated.
If you want the model to reach more, you have to grant more, explicitly and in code, where it shows up in code review and in the generated audit trail.

Where to go next

Skills

How skills controls what the model can reach.

Deterministic tool calls

Call integration tools directly from code without the LLM.

Human-in-the-loop

Require approval before specific tools execute.

TypeScript SDK reference

Full reference for TerseAgent.create, run, and runAndWait.