Drop an agent into your SaaS,
in 5 minutes.

Your REST API is the toolset. The agent acts on behalf of your user. The agent is a folder in your repo.

Get started
early access · go & typescript SDKs · self-host available · already have an account?
tavora cli · 80×24
$ tavora login
logged in as you@company.com
 
$ tavora init
created tavora/ with starter agent
 
$ tavora dev
watching tavora/ — synced draft (agent_id: ag_7f2a…)
app/components/CustomerSidebar.tsx
1 // in your React app:
2 import { TavoraChat } from "@tavora/react";
3  
4 <TavoraChat
5 agent="ag_7f2a…"
6 tenant={orgId}
7 />

Screen 2 · The folder

Your agent is files in your repo.

Standard files. Standard formats. Edited by Claude Code, reviewed in PRs, deployed by CLI. The source of truth is your repo, not a vendor database.

That's a real agent — Trello-style demo, copy-pasteable see it on GitHub →
tavora/
├── tavora.jsonc                 # project manifest
├── agents/
│   └── copilot/
│       ├── agent.jsonc          # model · capabilities · skills · evals
│       ├── persona.md           # system prompt
│       ├── skills/
│       │   └── board/main.js    # require()-able module · your REST adapter
│       └── evals/
│           └── basic.json       # { input, criteria, pass_threshold }
└── .runs/                       # session logs — gitignored

Screen 3 · How it works

One data path. No surprises.

Your user clicks the chat. The agent's plan is JavaScript you can read. Calls go out as the user, against your existing API. That's the whole loop.

01
User

signed into your app · holds a JWT

mounts in your frontend, passes the user's identity
02
<TavoraChat />

React component in your UI

opens a streaming session, gets the user's JWT
03
Tavora sandbox

LLM emits JS · runs in a JS sandbox · trace on disk

require()-able from the LLM's think loop
04
adapter.js

your code · in your repo · ~20 lines

fetch() with Authorization: Bearer ${session.jwt}
05
Your REST API

PocketBase / Supabase / your own Go

The agent can only do what the user can already do. No new permission surface, no new audit trail.

"Agents that reason by writing code" is the implementation that makes the rest of this readable, debuggable, and replayable.

Screen 4 · What you'd build

Multi-step agents your users
can actually rely on.

Each one is a thin .js adapter over your existing API. Cursor and Claude Code write these in minutes from your OpenAPI spec.

Customer support support.js

Triage, route, and resolve tier-1 tickets

Classify priority, look up the customer in your CRM, draft a reply with citations, escalate to oncall when stuck.

getTicket(id) {  return req('GET', '/tickets/' + id);},replyToTicket(id, body) {  return req('POST', '/tickets/' + id + '/reply', { body });},
Internal ops refunds.js

Refund eligibility & approval flows

Check the policy matrix, run the calculation, request human approval over a threshold.

getOrder(id) {  return req('GET', '/orders/' + id);},requestApproval(amount, reason) {  return req('POST', '/approvals', { amount, reason });},
Onboarding project.js

Guided project setup

Reads the user's data, asks clarifying questions, calls your APIs to make changes — with confirmations.

getProject(id) {  return req('GET', '/projects/' + id);},updateProject(id, patch) {  return req('PATCH', '/projects/' + id, patch);},
Analytics analytics.js

Natural-language queries on your data

A user asks in plain English; the agent writes the query against your schema, executes it, renders the result inline.

describeSchema() {  return req('GET', '/schema');},runQuery(sql) {  return req('POST', '/query', { sql });},

Each adapter file is ~20 lines on a real API. The JWT lives in the egress shim, not in this code.

See all 10 patterns across 4 buckets

Screen 5 · The eval story

Promotion is gated by your tests.

Evals are JSON files in the same folder. tavora dev runs them on every save. Behavior changes are reviewed like code changes — diffable in a PR, blocking a deploy when they regress.

agents/copilot/evals/creates_card.json
{
  "name": "adds a card to the named list",
  "input": "Add a card called \"order coffee\" to Todo.",
  "criteria": "Trace contains a single board.createCard call with listId 'l_todo' and title 'order coffee'. No other writes.",
  "pass_threshold": 7
}
failing run · draft below threshold
$ tavora dev⠋ syncing draft… synced (sha256:800b43c…)⠋ running 4 eval cases against the draft   ✓ basic                          9.1  ✓ adds a card to the named list  8.4  ✗ moves a card across lists      4.2  ← below 7.0  ✓ refuses to delete without yes  9.6   3 / 4 passing · log: .runs/2026-05-22T08-30-12Z-…
after one persona.md fix · ready to deploy
$ tavora dev⠋ syncing draft… synced (sha256:9c4f17b…)⠋ running 4 eval cases against the draft   ✓ basic                          9.1  ✓ adds a card to the named list  8.4  ✓ moves a card across lists      8.7  ✓ refuses to delete without yes  9.6   4 / 4 passing · ready to deploy

No bolt-on eval product. No "set up tracing later." The pass/fail signal is on disk, in the same loop the AI coding tool already uses.

Screen 6 · Security

The agent calls your backend as the user.

The sandbox attaches the user's JWT at the egress shim. The adapter JS never sees it, can't log it, can't leak it. Your backend authorizes the call the same way it authorizes a normal request from the user's browser.

fetchPolicies — real, from the demo agent.jsonc
// agents/copilot/agent.jsonc
"fetchPolicies": [
  {
    "origin": "${env.BACKEND_URL}",
    "headers": {
      "Authorization": "Bearer ${session.jwt}"
    }
  }
]
  • JWT lives in the egress shim

    Templates in agent.jsonc — never in the .js code, never in a vault you have to rotate. The session passes the user's token; the runtime stamps the header.

  • Tenant scoping is a prop

    Pass the tenant on <TavoraChat tenant={orgId} />. The runtime makes the session carry it; your backend filters by it. Same agent, every tenant, scoped by the call.

  • No new permission surface

    The agent can only do what the user can already do. No service account to audit. No "agent role" your security team has to model.

  • Hardened JS sandbox

    No filesystem, no subprocess, no arbitrary network. Memory and time are capped. Outbound fetches go through policies, not raw fetch.

"The agent can only do what the user can do" is a one-line answer to the scariest question a CTO will ask.

Your stack

Tavora your existing backend.

We don't replace your CRUD. We run the agent on top of whatever you've already got — your REST API is the toolset, your auth is the auth, your DB stays your DB.

Not on the list? If it speaks HTTP and JSON, it works.

Drop an agent into your SaaS, in 5 minutes.

Early access — limited spots while we onboard the first wave. Tell us about your app or grab a 30-minute slot.