TerseAgent is the runtime your handler uses to reason and act. It wraps the OpenAI Agents SDK under the hood, so you get the same agentic loop, tool-calling, and streaming model. On top of that, it adds the things workflows actually need: typed deterministic tool wrappers, structured outputs validated with zod, and a strict allowlist that prevents the model from touching anything you didn’t explicitly grant.
You create one inside onTrigger with TerseAgent.create() and then either call tools deterministically, hand off to the model with run() / runAndWait(), or both.
Agentic loop
run() and runAndWait() execute a full agentic loop on top of the OpenAI Agents SDK: 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.
| Method | Returns | Use when |
|---|---|---|
run(message) | Async iterable of events | You want to stream progress (text deltas, tool calls) |
runAndWait(message) | Final string | You want the final text once the loop completes |
runAndWait(message, schema) | Validated typed object | You want structured, schema-validated JSON (see below) |
skills. Everything else is invisible to it: your environment variables, your other integrations, and your other workflows.
Structured outputs
Pass azod schema as the second argument to runAndWait and the SDK forwards it to the model, parses the final JSON, and validates it before returning. The result is fully typed.
Strict ACL on skills and resources
The most important property ofTerseAgent is what it won’t do. Even though the underlying OpenAI Agents SDK can call arbitrary tools, 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 inskills, 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.
Attio.skill({ lists: [AttioList.Pipeline.NewDeals] })means the model can read and update records in NewDeals only, not your other lists or your other Attio workspaces. Slack.skill({ channel: SlackChannel.DealDesk })pins messaging to one channel. The model can’t@hereyour#general.GitHub.skill({ repos: [...] })restricts code access to specific repositories.- Tools listed in
toolApprovalsare paused for human approval before execution.
agent.tools.*, so the rules in your code are the rules in production.
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.