Agent Reads and Writes
This example walks through the core SDK workflow: creating a repo, writing files,
reading them back with metadata, and using transactions with expectedHead for
safe concurrent updates.
Full example
Section titled “Full example”import { Client, StaleHeadError, isStaleHeadError,} from "@githosted/sdk";
const client = new Client({ token: process.env.GITHOSTED_TOKEN, // baseUrl defaults to https://api.githosted.dev});
async function main() { // 1. Create a new repo const repo = await client.createRepo("agent-workspace"); console.log(`Created repo: ${repo.info?.slug} (${repo.id})`);
// 2. Write a config file const writeResult = await repo.write( "config.json", JSON.stringify({ model: "claude-3", temperature: 0.7 }, null, 2), { message: "Initialize config" }, ); console.log(`Commit: ${writeResult.commitSha}`);
// 3. Read the file back -- returns content + metadata for concurrency control const file = await repo.read("config.json"); console.log(`Content: ${file.content}`); console.log(`Head SHA: ${file.headSha}`); console.log(`Blob SHA: ${file.blobSha}`);
// 4. Write again with expectedHead for optimistic concurrency. // If another writer committed between our read and this write, // the server rejects with StaleHeadError instead of silently overwriting. const config = JSON.parse(file.content); config.temperature = 0.5;
await repo.write( "config.json", JSON.stringify(config, null, 2), { message: "Lower temperature", expectedHead: file.headSha, }, );
// 5. Use a transaction to write multiple files atomically await repo.transaction( "Add prompt and context files", async (tx) => { await tx.write("prompts/system.txt", "You are a helpful assistant."); await tx.write("prompts/user.txt", "Summarize the following document."); await tx.write( "context/metadata.json", JSON.stringify({ createdAt: new Date().toISOString() }), ); }, );
// 6. List files to verify const rootEntries = await repo.ls(); console.log("Root:", rootEntries); // [ // { name: "config.json", type: "file" }, // { name: "context", type: "directory" }, // { name: "prompts", type: "directory" }, // ]
const promptEntries = await repo.ls("prompts"); console.log("Prompts:", promptEntries); // [ // { name: "system.txt", type: "file" }, // { name: "user.txt", type: "file" }, // ]
// 7. View the commit log const commits = await repo.log({ limit: 5 }); for (const c of commits) { console.log(`${c.hash.slice(0, 7)} ${c.subject} (${c.authorName})`); }
// 8. Diff between two commits if (commits.length >= 2) { const diff = await repo.diff(commits[1].hash, commits[0].hash); console.log(diff.patch); }}
main().catch(console.error);Handling StaleHeadError
Section titled “Handling StaleHeadError”When multiple agents or processes write to the same repo, expectedHead prevents
lost updates. If the branch has moved, the SDK throws StaleHeadError with the
actual head SHA so you can re-read and retry:
async function safeUpdate(repo, path: string, transform: (content: string) => string) { for (let attempt = 0; attempt < 3; attempt++) { const file = await repo.read(path); const updated = transform(file.content);
try { return await repo.write(path, updated, { message: `Update ${path}`, expectedHead: file.headSha, }); } catch (err) { if (isStaleHeadError(err)) { console.log(`Stale head (attempt ${attempt + 1}), re-reading...`); console.log(`Expected: ${err.expectedHead}, actual: ${err.actualHead}`); continue; // re-read and retry } throw err; } } throw new Error(`Failed to update ${path} after 3 attempts`);}Transactions with expectedHead
Section titled “Transactions with expectedHead”Transactions also accept expectedHead to guard the entire batch of changes:
const file = await repo.read("state.json");
await repo.transaction( "Update state and log", async (tx) => { const state = JSON.parse(file.content); state.step = state.step + 1; await tx.write("state.json", JSON.stringify(state)); await tx.write("logs/step-" + state.step + ".txt", "Completed step"); await tx.delete("logs/step-" + (state.step - 10) + ".txt"); }, { expectedHead: file.headSha },);Key points
Section titled “Key points”repo.read()returnsheadSha— pass it asexpectedHeadon the next write to enable optimistic concurrency.StaleHeadErroris never auto-retried — you must handle it explicitly, since the SDK cannot know how to merge your intent with the intervening changes.RepoBusyErroris auto-retried — the SDK retries with exponential backoff (up to 3 times by default) when the repo is temporarily locked by another in-progress write.- Transactions commit multiple file changes under a single commit message.
They accept the same
refandexpectedHeadoptions as individual writes.