ai-sdk-harness

Working on a repository

Give each session of your agent a checkout of your repository with ai-sdk-harness-workspace, and take what it changed back as a patch.

This guide starts from the chat of Managing sessions and puts a repository in it: each conversation works in a checkout of its own, so several agents work on one repository at once without stepping on each other, and what an agent changed comes back to your server as a patch, which your server commits and pushes with its own identity. Every option is in the package's documentation.

Install the package

pnpm add ai-sdk-harness-workspace

Pick where the code comes from

From a Git server, the sandbox fetches it; a private repository's token stays on your server, the sandbox's proxy putting it in the requests on their way out:

import { gitRepository } from 'ai-sdk-harness-workspace';

const source = gitRepository({
  url: 'https://github.com/acme/widgets.git',
  credentials: async () => ({ token: await installationToken('acme/widgets') }),
});

Or from a clone on your server, which you keep up to date: the sandbox gets Git bundles of what it lacks, and nothing in it can reach the repository.

import { hostRepository } from 'ai-sdk-harness-workspace';

const source = hostRepository({ path: '/srv/repositories/widgets', ref: 'main' });

Create the workspace

Give the agent the workspace, and the session manager its sandboxes. setup runs in each new checkout before the agent starts:

lib/sessions.ts
import { createSessionManager, sharedSandbox } from 'ai-sdk-harness-sessions';
import { createWorkspace, withWorkspace } from 'ai-sdk-harness-workspace';

export const workspace = createWorkspace({ source, setup: ['pnpm install --frozen-lockfile'] });

const agent = new HarnessAgent(withWorkspace({ harness, model: 'claude-sonnet-5-5' }, workspace));

export const sessions = createSessionManager<{ title: string }>({
  agent: () => agent,
  sandboxes: workspace.sandboxes(
    sharedSandbox({ open: openSandbox, ports: Array.from({ length: 4 }, (_, i) => 4001 + i) }),
  ),
  idleTimeoutMs: 10 * 60_000,
});

openSandbox is the function of Managing sessions: the workspace takes whatever sandbox it opens, a Docker Sandbox, a Cloud Run sandbox or another.

A new session now starts with the repository checked out in its working directory, on the branch session/<id>. A suspended one keeps its checkout, and finds it again when it resumes; a closed one has it removed.

Show what the agent changed

A route lists the files the session changed since its checkout started, its commits included:

app/api/sessions/[id]/changes/route.ts
import { workspace } from '@/lib/sessions';

export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
  const checkout = workspace.session((await params).id);
  if (checkout === undefined) return new Response('The session is not live.', { status: 409 });
  return Response.json({ base: checkout.base, files: await checkout.changedFiles() });
}

Commit and push it from your server

changes() gives the work as a binary patch. Apply it on a clone of your server at the commit the checkout started from, and push it with your server's credentials: the agent never held them.

lib/publish.ts
import { execFile } from 'node:child_process';
import { writeFile } from 'node:fs/promises';
import { promisify } from 'node:util';

const git = (...args: string[]) => promisify(execFile)('git', ['-C', CLONE, ...args]);

export async function publish(id: string, title: string) {
  const checkout = workspace.session(id);
  if (checkout === undefined) throw new Error('The session is not live.');
  const branch = `agent/${id}`;
  await writeFile(`/tmp/${id}.patch`, await checkout.changes());
  await git('fetch', 'origin');
  await git('checkout', '-B', branch, checkout.base);
  await git('apply', '--binary', '--index', `/tmp/${id}.patch`);
  await git('commit', '-m', title);
  await git('push', '--force-with-lease', 'origin', branch);
}

Read the patch before you publish it: it is what the agent made it.

Next

  • Several repositories — an API, a web app, services: give the workspace repositories in place of source, each checked out in a directory of the session's working directory. See Several repositories.
  • Give the agent a tool that runs your test suite in its checkout, with workspace.fromSandbox(): see Tools that work in the checkout.
  • Keep the agent from pushing or deleting on its own with an approval policy: deny: [{ match: 'git push', reason: 'The application publishes the changes.' }].

On this page