Agents API

Quickstart

Run a task in a hosted sandbox, continue the conversation, then have an agent change code in a repository.

Prerequisites

  • A LatentStack API key. It starts with ls-, and you can copy it from your Account page.
  • A model on your tier. The examples use bedrock/us.anthropic.claude-sonnet-5; latentcode models lists yours.
  • For step 3, a GitHub token on your Agent Settings page that can read the repository.
$export LS_API_KEY="ls-…"

Install the SDK

The SDKs are distributed by LatentStack, not npm or PyPI. Install from the URL:

npm install https://latentstack.dev/dl/latentcode-sandbox-agents-0.1.1.tgz

TypeScript runs on Node 22+, Bun and Deno, with no dependencies. The examples use top-level await, so run them as ES modules, for example with npx tsx quickstart.ts. Python needs 3.10+.

1. Run a task

This session has no repository: the agent works in an empty sandbox with a shell, Python 3 and Node.js. The agent normally asks before running shell commands. There's nothing to protect in an empty sandbox, so this example allows them.

Run a task and print the reply
import { LatentStack } from "@latentcode/sandbox-agents"

const client = new LatentStack({ apiKey: process.env.LS_API_KEY! })
const sessions = client.agents.sessions

const session = await sessions.create({
  agent: {
    model: "bedrock/us.anthropic.claude-sonnet-5",
    instructions: "Answer briefly.",
    // An empty sandbox has nothing to protect, so let the agent run commands without asking.
    permissions: [{ action: "bash", effect: "allow" }],
  },
  input: "Write a Python script that prints the first 10 prime numbers, run it, and show me the output.",
})

for await (const event of sessions.events(session.id)) {
  if (event.type === "tool.called") console.log("tool:", event.data?.tool)
  if (event.type === "text.ended") console.log(event.data?.text)
  if (event.type === "run.completed" || event.type === "run.failed") break
}
tool: bash
I created primes.py, which checks divisibility up to the square root of each candidate, and ran it.

Output:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

events() yields everything the agent does as it happens. The loop stops at run.completed, when the agent has finished the task.

2. Continue the conversation

A session keeps its conversation and its files. Send another message, and read only the new events by passing after:

Send a follow-up message
const { last_seq } = await sessions.get(session.id)
await sessions.message(session.id, "Now make it print the first 20.")

for await (const event of sessions.events(session.id, { after: last_seq })) {
  if (event.type === "text.ended") console.log(event.data?.text)
  if (event.type === "run.completed" || event.type === "run.failed") break
}

await sessions.delete(session.id)
Updated the loop to collect 20 primes and ran it.

Output:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

3. Change code in a repository

Give the session a repository and a task, then download what it changed as a patch. The agent works on its own clone, and nothing is pushed.

Add a file to a repository and get the patch
import { writeFile } from "node:fs/promises"
import { LatentStack } from "@latentcode/sandbox-agents"

const client = new LatentStack({ apiKey: process.env.LS_API_KEY! })
const sessions = client.agents.sessions

const session = await sessions.create({
  agent: {
    model: "bedrock/us.anthropic.claude-sonnet-5",
    instructions: "Keep changes small.",
    // This task only needs the file tools. Denying the shell also means the agent never stops to ask.
    permissions: [{ action: "bash", effect: "deny" }],
  },
  environment: { type: "repos", repos: ["acme/demo"] },
  input: "Add a CONTRIBUTING.md that explains how to set up this project and run its tests, based on what you find in the repository.",
})

try {
  for await (const event of sessions.events(session.id)) {
    if (event.type === "text.ended") console.log(event.data?.text)
    if (event.type === "run.completed" || event.type === "run.failed") break
  }

  const patch = await sessions.artifact(session.id)
  await writeFile("contributing.patch", patch) // apply with: git apply contributing.patch
  console.log(patch) // a unified diff adding CONTRIBUTING.md
} finally {
  await sessions.delete(session.id)
}

The patch is a unified diff against the commit the agent started from. Apply it with git apply contributing.patch, or open a pull request with it.

Why the example denies the shell
By default the agent asks before most shell commands. A script that doesn't answer would wait forever. This task only needs the file tools, so denying the shell keeps it unattended. See Approvals to answer instead.

Next steps