Agents APIWorkspace

Repositories and patches

Give an agent your GitHub repositories on any branch, tag or commit, and get back what it changed as a patch.

Environments

environmentThe agent gets
omitted, or { type: "none" }An empty sandbox with a shell, git, Python 3 and Node.js. No GitHub setup, and no patch.
{ type: "repos", repos: [...] }Up to six of your repositories, each cloned into its own folder. One patch per repository.

Repository sessions need a GitHub token that can read every listed repository. Save one on your Agent Settings page. LatentStack clones for the agent, so the token never enters the sandbox. Creating a session checks access first, and fails with github_not_connected or repo_not_accessible (naming the repository) if something is missing.

Choose where to start

A repository listed as "owner/name" starts on its default branch. Use { repo, ref } to start on a branch, tag or commit. With a single repository, a top-level ref does the same.

One repository, on a pull request's branch
environment: { type: "repos", repos: ["acme/payments"], ref: "feature/refunds" }
Several repositories, each on its own ref
const session = await sessions.create({
  agent: {
    model: "bedrock/us.anthropic.claude-sonnet-5",
    instructions: "Keep changes minimal.",
    permissions: [{ action: "bash", effect: "deny" }], // file tools only
  },
  environment: {
    type: "repos",
    repos: [
      "acme/demo",                             // its default branch
      { repo: "acme/website", ref: "master" }, // a branch, tag or commit
    ],
  },
  input: "In each repository, add a line at the end of the README: 'Maintained by the platform team.'",
})

The workspace

one repository                  several repositories
/work/payments  ← agent starts  /work            ← agent starts here
                  here          ├── demo/
                                └── website/
  • With several repositories, the agent is told which folder holds which repository before it reads your instructions. Folder names come from the repository name, so two repositories can't share one.
  • Git works as usual inside each clone (status, diff, log, show, fetch, switching branches), but pushing isn't possible. You get the changes as patches.

Patches

A patch is the diff of one repository against the commit it started from, including new, deleted and binary files. It applies with git apply. Patches are saved when a run ends, when the session pauses and when it's deleted, so you can download them after the sandbox is gone. While the session is active, you always get the current state.

Change one repository and save 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)
}

Several repositories

artifacts() lists one entry per repository with a summary of changed files. Download each patch by naming its repository:

List the changes, then save each patch
const { data } = await sessions.artifacts(session.id)
for (const change of data) {
  console.log(change.repo, (change.files ?? []).map((f) => `${f.status} ${f.file} +${f.additions} -${f.deletions}`))
  const patch = await sessions.artifact(session.id, "patch", { repo: change.repo! })
  await writeFile(`${change.repo!.split("/")[1]}.patch`, patch)
}
FieldMeaning
repo · base_commitThe repository, and the commit the patch applies to.
filesEach changed file with status (added, modified or deleted) and line counts.
size · finalSize in bytes; final is true once the session is deleted and the patch can't change.
Size limit
A patch can be up to 15 MB; larger ones fail with patch_too_large. Keep build output and generated files out of the change.