Sinatra Docs

SDKs

Official Sinatra clients for Node.js, Python and Go, to start, follow and stop tasks from your own code.

Sinatra has official clients for Node.js, Python and Go. They wrap the HTTP API, so a task started from code runs like one started from the CLI or MCP: it gets a sandbox, can open a pull request, and shows up in the dashboard.

LanguagePackageNeedsSource
Node.js@sinatra-dev/sdkNode 20 or newersinatra-dev/sinatra-node
Pythonsinatra-devPython 3.11 or newersinatra-dev/sinatra-python
Gogithub.com/sinatra-dev/sinatra-goGo 1.24 or newersinatra-dev/sinatra-go

Create a personal access token on the CLI & API page of the dashboard, as described in Create a token, and set it as SINATRA_TOKEN. Each client reads it from there, or takes it as an option.

Every quick start below does the same three things: reads who the token acts as, starts a task in your first workspace with an idempotency key, and prints the conversation until the turn ends. The API has no streaming, so each one polls. Each loop reads the task's status before the conversation, so it never stops before printing the reply that ended the turn. See How replies work for when a turn is done.

Node.js

npm install @sinatra-dev/sdk
import { Sinatra } from "@sinatra-dev/sdk";

const sinatra = new Sinatra(); // reads SINATRA_TOKEN
const me = await sinatra.me.get();
const workspace = me.workspaces[0];
if (!workspace) throw new Error("The token's user isn't in any workspace yet.");

const { task } = await sinatra.tasks.create(
  workspace.id,
  { prompt: "Fix the flaky login test", repo: "acme/web" },
  { idempotencyKey: "fix-flaky-login-1" },
);

// The turn is done when the task leaves these statuses (for example, it reaches replied or pr_open).
const WORKING = new Set(["pending", "investigating", "implementing", "rework"]);
let after: string | undefined;
for (;;) {
  const { status } = await sinatra.tasks.get(workspace.id, task.id);
  for await (const m of sinatra.messages.listAll(workspace.id, task.id, { after })) {
    console.log(`${m.role}: ${m.body}`);
    after = m.id;
  }
  if (!WORKING.has(status)) break;
  await new Promise((r) => setTimeout(r, 3000));
}

Python

pip install sinatra-dev
import time

from sinatra_dev import Sinatra

# The turn is done when the task leaves these statuses (for example, it reaches replied or pr_open).
WORKING = {"pending", "investigating", "implementing", "rework"}

with Sinatra() as sinatra:  # reads SINATRA_TOKEN
    me = sinatra.me.get()
    if not me.workspaces:
        raise SystemExit("The token's user isn't in any workspace yet.")
    workspace = me.workspaces[0]

    task = sinatra.tasks.create(
        workspace.id, "Fix the flaky login test", repo="acme/web", idempotency_key="fix-flaky-login-1"
    ).task

    after = None
    while True:
        status = sinatra.tasks.get(workspace.id, task.id).status
        for m in sinatra.messages.list_all(workspace.id, task.id, after=after):
            print(f"{m.role}: {m.body}")
            after = m.id
        if status not in WORKING:
            break
        time.sleep(3)
    print(f"Task {task.id} is {status}.")

The Python client is synchronous. There is no async client yet.

Go

go get github.com/sinatra-dev/sinatra-go

The package name is sinatra. This is the body of main, where check calls log.Fatal on an error. The full program has the imports.

ctx := context.Background()
client, err := sinatra.NewClient() // reads SINATRA_TOKEN
check(err)
me, err := client.Me.Get(ctx)
check(err)
if len(me.Workspaces) == 0 {
	log.Fatal("the token's user isn't in any workspace yet")
}
ws := me.Workspaces[0].Id
created, err := client.Tasks.Create(ctx, ws, sinatra.CreateTaskRequest{Prompt: "Fix the flaky login test"},
	sinatra.WithIdempotencyKey("fix-flaky-login-1"))
check(err)
working := map[string]bool{"pending": true, "investigating": true, "implementing": true, "rework": true}
var after *string
for ; ; time.Sleep(3 * time.Second) {
	task, err := client.Tasks.Get(ctx, ws, created.Task.Id)
	check(err)
	for m, err := range client.Messages.ListAll(ctx, ws, task.Id, &sinatra.MessagesListParams{After: after}) {
		check(err)
		fmt.Printf("%s: %s\n", m.Role, m.Body)
		after = &m.Id
	}
	if !working[task.Status] {
		fmt.Println("status:", task.Status)
		return
	}
}

How they behave

The clients are generated from the API's OpenAPI spec, and a new version is released automatically when the API changes. All three behave the same way:

  • Token. They read SINATRA_TOKEN, or take the token as an option. Creating a client without one fails.
  • Retries. A request that failed with 429 or 503 is retried up to 3 times when repeating it is safe. A 429 waits for the Retry-After the server sends, at most 60 seconds; a 503 waits 0.5, 1 and 2 seconds. Starting a task and sending a message always carry an idempotency key, and every retry reuses it, so a retry never starts a second task or sends a message twice. If you don't pass a key, the client makes one. Pass your own to make a retry safe across process restarts too. Stopping a task retries on 503 only, delegating an issue is never retried, and timeouts and network failures are never retried.
  • Paging. tasks.list_all and messages.list_all (listAll in Node.js, ListAll in Go) are iterators that fetch every page for you.
  • Errors. An error response becomes one SinatraError (*sinatra.Error in Go) with the HTTP status, the API's error code, a reason written for a person, and the requestId (request_id in Python). Include the request id when you contact support. Network failures and timeouts aren't wrapped; they surface as your language reports them.
  • New values. Fields such as status, source, role and the error code are plain strings. When the API adds a value, the clients keep working; handle values you don't recognise.

The clients are 0.x. While the major version is 0, a breaking API change bumps the minor version, so pin the version you've tested. Each repository's README lists every method.

Next