Skip to content

Use githosted in an agent

The pattern that makes githosted earn its keep inside an agent loop: one repo per session. Every run of the agent gets its own repo. Every file the agent writes is a real Git commit. When the agent fails — and it will — you don’t lose state. You diff, roll back, or branch a new attempt off the last good one.

Mint a workspace + write token from the dashboard. The agent container reads it from GITHOSTED_TOKEN:

Terminal window
GITHOSTED_TOKEN=gw_…
from githosted import Client
client = Client() # reads GITHOSTED_TOKEN

Workspace scope is right because the agent will create new repos on every run. A repo-scoped token can’t create new repos. See Authenticate for the wider picture and Sandbox auth for the “how do I get the token into a Modal / Daytona / E2B sandbox” question.

The headline. The agent gets a run_id from somewhere (a UUID, a job ID, a timestamp), creates a repo named after it, and writes its work into that repo.

import uuid
from githosted import Client
client = Client()
run_id = uuid.uuid4().hex[:8]
repo = client.create_repo(f"agent-run-{run_id}")

The repo’s slug becomes part of a stable URL the rest of your system can reference:

app.githosted.dev/<workspace>/agent-run-<run_id>

From here, every file the agent produces is a commit:

repo.write(
"plan.md",
plan_text,
message="Initial plan",
)
repo.write(
"src/handler.py",
generated_code,
message="Generated handler",
)

A successful run produces a repo full of artifacts plus a clean commit history. A failed run produces the same — except you can see exactly where it went wrong.

Don’t, by default. Keeping the run repos around is the point. A weekly cron that prunes repos older than N days is a fine cleanup story when storage matters; in early development just leave them.

Pattern 2: shared repo, branch per session

Section titled “Pattern 2: shared repo, branch per session”

When runs are tightly related (e.g. iterating on the same project, each run is a step) it’s often more useful to share one repo across many runs and branch per session:

repo = client.repo("project-x") # existing repo
repo.create_branch(f"run/{run_id}", from_ref="main")
repo.write(
"src/handler.py",
generated_code,
ref=f"run/{run_id}",
message="Tweak handler logic",
)

The branch name preserves the per-run isolation. git log run/<run_id> shows what that run changed. If the run is good, merge into main. If not, delete the branch.

# good run
repo.merge(source=f"run/{run_id}", into="main")
# bad run
repo.delete_branch(f"run/{run_id}")

Pattern 3: read prior runs to inform the next one

Section titled “Pattern 3: read prior runs to inform the next one”

The agent’s prior commits are first-class context for the next prompt. Read them back exactly the way an editor would:

last_attempt = repo.read("src/handler.py", ref="run/abc123").content
last_diff = repo.diff("run/abc123", "run/def456")

Feed these into the next LLM call so the agent knows what’s already been tried, what changed between attempts, and what worked. This is the move that makes “agentic coding” work without turning into a goldfish that forgets every five minutes.

The killer move. When you have two runs of the same task, compare them:

delta = repo.diff(
base_ref="run/attempt-1",
head_ref="run/attempt-2",
)
for f in delta.files:
print(f"{f.status} {f.path} (+{f.additions}/-{f.deletions})")

Surface the diff to the agent itself for self-critique, surface it to a human reviewer for spot-checks, or feed it into a tests-pass / tests-fail comparison.

Every repo.write is a commit by default. When a single agent step produces several files that belong together — src/foo.py, its tests, and an updated README.md — wrap them in a transaction so they land as one commit:

with repo.transaction("Add new handler with tests") as tx:
tx.write("src/handler.py", code)
tx.write("tests/test_handler.py", tests)
tx.write("README.md", readme)

One commit, one rollback unit, one diff target. See Transactions.

The simplest rollback is “don’t merge that branch.” More direct, when the run wrote to a long-lived branch and you want to undo:

# Find the SHA you want to roll back to
log = repo.log(limit=20)
good = next(c for c in log if "Generated handler" in c.subject)
# Reset by force-creating a branch at the good SHA, and
# re-pointing main to it via merge.
repo.create_branch("rollback", from_ref=good.sha)
repo.merge(source="rollback", into="main")

Real-world: cheap, scriptable, requires no human approval flow.

The two errors you’ll actually hit:

  • ConflictError (HTTP 409) — another writer holds the branch lease. Wait briefly and retry. Different branches never collide, so per-session branches sidestep this entirely.
  • StaleHeadError (HTTP 412) — you passed expected_head to repo.write and the branch moved underneath you. Re-read the current head, recompute, retry. The transaction API uses this pattern automatically inside with repo.transaction(...).

For raw repo.write calls in a tight retry loop:

import time
for attempt in range(3):
try:
repo.write("src/main.py", code, message="Update")
break
except ConflictError:
time.sleep(2 ** attempt)

See Errors for the full list.

The agent app builder example — a CLI that takes a prompt, calls Claude, and writes the generated app into a fresh repo seeded from a public Vite + React template — uses every pattern on this page in ~200 lines. Bring your own LLM key and your own githosted workspace token; the README walks through cloning, configuring, and running it locally.

What this gives you that bare LLM loops don’t

Section titled “What this gives you that bare LLM loops don’t”
  • A persistent record of what the agent did, in the standard format (Git) every other tool already speaks.
  • A diff between any two runs in two lines. No “remember to serialize state” boilerplate.
  • A rollback that’s a merge away.
  • A shareable URL per run when the repo is public.
  • Quickstart — sign in, mint a token, write your first file
  • Authenticate — picking the right token for the agent
  • Transactions — atomic multi-file commits
  • Errors — what to retry, what to bubble up