Skip to content
295 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The balance shows what was spent this month and in total1//! Runs a coding agent on one pull request and reports back to g1t.
2//!
3//! This is the program a hosted sandbox starts. It clones the pull
4//! request's fork, runs the agent harness headless, streams what the agent
5//! does into the pull request's session as it happens, pushes the result
6//! and marks the pull request ready for review. It talks to g1t only through the public API and git, exactly
7//! as an agent on someone's own machine would.
8//!
9//! `MODE` selects another job instead: `checks` runs acceptance checks,
10//! `update` brings a pull request up to date with its target branch,
11//! `review` has an agent review one, and `revise` sends the author back to
12//! address what the checks or a review found, `plan` turns an outcome
13//! into issues, `queue` builds and checks a state of the merge queue,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily14//! `mergecheck` finds out whether a pull request merges cleanly,
Merge branch 'worktree-agent-ac5b181a013e54348'15//! `actions` runs one job of a GitHub Actions workflow, `backup` cuts a
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.16//! repository's nightly backup bundle, `bump` makes a
17//! security update: one package raised in its lockfiles, pushed as g1t,
18//! and `computer` is a workspace agent's own computer, served for as long
19//! as it is awake. See the modules of those names.
The balance shows what was spent this month and in total20//!
21//! Configuration comes from the environment:
22//!
23//! - `G1T_API`, `G1T_TOKEN`, `G1T_USER`: where and who to report as.
24//! - `G1T_REPO`, `PULL_NUMBER`, `GIT_REMOTE`: the pull request and its fork.
25//! - `PROMPT`: what the agent is asked to do.
26//! - `COMMIT_MESSAGE`: used if the agent leaves changes uncommitted.
27//! - `ANTHROPIC_API_KEY`: read by the harness itself.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look28//!
29//! Every mode runs with the mining watch in `abuse`: a sandbox that looks
30//! like it is mining stops itself and exits with `abuse::EXIT_CODE`.
Fast pages, required checks on the branch, self-hosted runners, honest incidents31//!
32//! Given a command instead (`register`, `run`, `service`, `remove`,
33//! `update`, `version`), it is a self-hosted runner on someone's own
34//! machine, which runs work in these modes: see `selfhosted`.
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.35//!
36//! `deploy` and `detect` are for a workflow step: a g1t.page build of the
37//! job's own checkout, and what that build would find (see `deploy`).
The balance shows what was spent this month and in total38
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39mod abuse;
The balance shows what was spent this month and in total40mod actions;
Merge branch 'worktree-agent-ac5b181a013e54348'41mod backup;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily42mod bump;
The balance shows what was spent this month and in total43mod checks;
Fast pages, required checks on the branch, self-hosted runners, honest incidents44mod clone;
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.45mod computer;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step46mod confidence;
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.47mod customize;
The balance shows what was spent this month and in total48mod deploy;
Merge branch 'main' into actions-toolkit-oidc-artifacts49mod docker;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API50mod guard;
The balance shows what was spent this month and in total51mod harness;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API52mod learned;
The balance shows what was spent this month and in total53mod mergecheck;
54mod plan;
55mod progress;
56mod queue;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API57mod reply;
The balance shows what was spent this month and in total58mod report;
59mod review;
60mod revise;
Fast pages, required checks on the branch, self-hosted runners, honest incidents61mod selfhosted;
The balance shows what was spent this month and in total62mod steer;
63mod update;
64
65use std::path::Path;
66use std::process::Command;
67
68use anyhow::{Context, Result, bail};
69use base64::Engine;
70use base64::engine::general_purpose::STANDARD;
71
72use report::{Entry, Reporter};
73
74pub(crate) const WORKDIR: &str = "/work/repo";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent75/// The name and address on every commit g1t makes here, whether its agent
76/// or g1t itself: `g1t_contracts::system::{USERNAME, EMAIL}`.
77pub(crate) const AUTHOR_NAME: &str = "g1t";
78pub(crate) const AUTHOR_EMAIL: &str = "g1t@users.noreply.g1t.sh";
The balance shows what was spent this month and in total79
80pub(crate) fn env(name: &str) -> Result<String> {
81 std::env::var(name).with_context(|| format!("{name} is not set"))
82}
83
84/// Runs git and returns its trimmed output, failing on a non-zero exit.
85pub(crate) fn git(dir: &Path, args: &[&str]) -> Result<String> {
86 let output = Command::new("git")
87 .current_dir(dir)
88 .args(args)
89 .output()
90 .context("could not run git")?;
91 if !output.status.success() {
92 bail!(
93 "git {} failed: {}",
94 args.first().unwrap_or(&""),
95 String::from_utf8_lossy(&output.stderr).trim()
96 );
97 }
98 Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned())
99}
100
101/// A git option that authenticates one command. The credential is passed
102/// per command and never written to the clone's config or its remote URL,
103/// where the agent would find it.
104pub(crate) fn auth_option(user: &str, token: &str) -> String {
105 let credentials = STANDARD.encode(format!("{user}:{token}"));
106 format!("http.extraHeader=Authorization: Basic {credentials}")
107}
108
109/// Clones the fork, runs the agent on `PROMPT`, commits and pushes what it
110/// did, and returns its closing summary.
111pub(crate) fn run(reporter: &mut Reporter) -> Result<String> {
112 let mut prompt = env("PROMPT")?;
113 let remote = env("GIT_REMOTE")?;
114 let auth = auth_option(&env("G1T_USER")?, &env("G1T_TOKEN")?);
115 let workdir = Path::new(WORKDIR);
116
Merge branch 'model-routing'117 // Which model, and why: the router's one line names both.
118 let reason = std::env::var("AGENT_MODEL_REASON").unwrap_or_default();
119 if !reason.trim().is_empty() {
120 reporter.record(Entry::new("note", reason.trim()));
121 } else if let Ok(model) = std::env::var("AGENT_MODEL_NAME") {
The balance shows what was spent this month and in total122 reporter.record(Entry::new("note", &format!("Running on {model}.")));
123 }
124 reporter.record(Entry::new("prompt", &prompt));
125 reporter.flush();
126
127 std::fs::create_dir_all("/work")?;
Fast pages, required checks on the branch, self-hosted runners, honest incidents128 clone::clone(Path::new("/work"), &auth, &[], &remote, WORKDIR).context("could not clone the pull request's fork")?;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent129 git(workdir, &["config", "user.name", crate::AUTHOR_NAME])?;
130 git(workdir, &["config", "user.email", crate::AUTHOR_EMAIL])?;
The balance shows what was spent this month and in total131 let branch = git(workdir, &["rev-parse", "--abbrev-ref", "HEAD"])?;
132 let start = git(workdir, &["rev-parse", "HEAD"]).unwrap_or_default();
133
134 // Sent back to work that is already open: start from where the branch it
135 // will land on is now, so what passes here passes there too.
136 if let (Ok(upstream), Ok(upstream_branch)) = (env("UPSTREAM_REMOTE"), env("UPSTREAM_BRANCH")) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents137 clone::fetch(workdir, &auth, &upstream, &upstream_branch).context("could not fetch the branch this will land on")?;
138 // Shallow: deep enough to tell whether it is behind, and to merge.
139 clone::share_history(workdir, &auth, &[("origin", branch.as_str()), (upstream.as_str(), upstream_branch.as_str())], "HEAD", "FETCH_HEAD")?;
The balance shows what was spent this month and in total140 let behind = Command::new("git")
141 .current_dir(workdir)
142 .args(["merge-base", "--is-ancestor", "FETCH_HEAD", "HEAD"])
143 .status()
144 .is_ok_and(|status| !status.success());
145 if behind {
146 let message = format!("Catch up with {upstream_branch}");
147 let merged = Command::new("git")
148 .current_dir(workdir)
149 .args(["merge", "--quiet", "--no-edit", "-m", &message, "FETCH_HEAD"])
150 .status()
151 .is_ok_and(|status| status.success());
152 if merged {
153 reporter.record(Entry::new(
154 "note",
155 &format!("Merged in the latest {upstream_branch} before starting."),
156 ));
157 } else {
158 let files = git(workdir, &["diff", "--name-only", "--diff-filter=U"])?;
159 let files: Vec<&str> = files.lines().collect();
160 reporter.record(Entry::new(
161 "note",
162 &format!(
163 "Merged in the latest {upstream_branch} before starting; {} conflict.",
164 files.join(", ")
165 ),
166 ));
167 prompt.push_str(&format!(
168 "\n\nBefore you started, the latest {upstream_branch} was merged into this branch, and these files conflict: {}. Resolve the conflicts first, keeping what both sides meant, then address the points above. Leave no conflict markers.",
169 files.join(", ")
170 ));
171 }
172 reporter.flush();
173 }
174 }
175
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step176 // The agent is asked what it learned, which goes to memory, and how sure
177 // it is of its change, which g1t weighs with what it observes. Neither
178 // stays in the summary.
179 let asked = confidence::ask(&learned::ask(&prompt));
180 let summary = confidence::finish(learned::finish(harness::run_claude(workdir, &asked, reporter)?));
The balance shows what was spent this month and in total181
182 // Commit whatever the agent left in the working tree.
183 if !git(workdir, &["status", "--porcelain"])?.is_empty() {
184 let message = std::env::var("COMMIT_MESSAGE").unwrap_or_else(|_| "Agent changes".into());
185 git(workdir, &["add", "--all"])?;
186 git(workdir, &["commit", "--quiet", "--message", &message])?;
187 }
188 let head = git(workdir, &["rev-parse", "HEAD"])?;
189 if head == start {
190 // An agent woken to answer usually only answers.
191 if std::env::var("MODE").as_deref() == Ok("answer") {
192 return Ok(summary);
193 }
194 bail!("the agent finished without changing anything");
195 }
196 git(
197 workdir,
198 &[
199 "-c",
200 &auth,
201 "push",
202 "--quiet",
203 "origin",
204 &format!("HEAD:{branch}"),
205 ],
206 )
207 .context("could not push the pull request's commits")?;
208 reporter.record(Entry::new(
209 "note",
210 &format!("Pushed {}.", &head[..head.len().min(12)]),
211 ));
212 Ok(summary)
213}
214
215fn main() {
Merge branch 'main' into actions-toolkit-oidc-artifacts216 // The runc the job's Docker Engine starts containers with (docker/oci.rs).
217 if docker::oci::invoked_as_runc() {
218 docker::oci::main();
219 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents220 // A self-hosted runner's commands; the modes below are what it, and
221 // g1t's sandboxes, run work with.
222 let args: Vec<String> = std::env::args().skip(1).collect();
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.223 // From a workflow step: a g1t.page build of the job's own checkout,
224 // and what it would find (deploy.rs).
225 match args.first().map(String::as_str) {
226 Some("deploy") => std::process::exit(deploy::cli(&args[1..])),
227 Some("detect") => std::process::exit(deploy::detect_cli(&args[1..])),
228 _ => {}
229 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents230 if selfhosted::is_command(&args) {
231 std::process::exit(selfhosted::main(args));
232 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API233 // A guarded sandbox's HTTPS is re-signed on its way out: trust that
234 // before anything is fetched. The guard hook runs before every tool
235 // call, so it skips this.
236 if std::env::var("MODE").as_deref() == Ok("guard") {
237 std::process::exit(guard::hook_main());
238 }
239 guard::trust_egress_ca();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look240 // Watches for mining for as long as the sandbox runs (abuse.rs). Not in
241 // the hooks the harness runs after every tool call.
242 if std::env::var("MODE").as_deref() != Ok("steer") {
243 abuse::watch();
244 }
The balance shows what was spent this month and in total245 // The same image does the other jobs a sandbox is started for.
246 match std::env::var("MODE").as_deref() {
247 Ok("actions") => std::process::exit(actions::main()),
Merge branch 'worktree-agent-ac5b181a013e54348'248 Ok("backup") => std::process::exit(backup::main()),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily249 Ok("bump") => std::process::exit(bump::main()),
The balance shows what was spent this month and in total250 Ok("checks") => std::process::exit(checks::main()),
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.251 // An agent's own computer: serves until it is put to sleep.
252 Ok("computer") => std::process::exit(computer::main()),
The balance shows what was spent this month and in total253 Ok("deploy") => std::process::exit(deploy::main()),
254 Ok("update") => std::process::exit(update::main()),
255 Ok("review") => std::process::exit(review::main()),
256 Ok("revise") => std::process::exit(revise::main()),
257 Ok("answer") => std::process::exit(revise::answer()),
258 Ok("plan") => std::process::exit(plan::main()),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API259 Ok("reply") => std::process::exit(reply::main()),
The balance shows what was spent this month and in total260 Ok("queue") => std::process::exit(queue::main()),
261 Ok("mergecheck") => std::process::exit(mergecheck::main()),
262 Ok("steer") => std::process::exit(steer::main()),
263 _ => {}
264 }
265 let mut reporter = match Reporter::from_env() {
266 Ok(reporter) => reporter,
267 Err(error) => {
268 eprintln!("g1t-runner: {error:#}");
269 std::process::exit(2);
270 }
271 };
272 match run(&mut reporter) {
273 Ok(summary) => {
274 reporter.flush();
275 if let Err(error) = reporter.ready(&summary) {
276 eprintln!("g1t-runner: could not mark the pull request ready: {error:#}");
277 std::process::exit(1);
278 }
279 }
280 Err(error) => {
281 eprintln!("g1t-runner: {error:#}");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API282 // Stopped at a cap: like a person's stop, the pull request is
283 // left open for a person, not closed.
284 if guard::is_halt(&error) {
285 reporter.record(Entry::new("note", &format!("g1t stopped the agent: {error:#}.")));
286 reporter.flush();
287 std::process::exit(1);
288 }
The balance shows what was spent this month and in total289 reporter.record(Entry::new("note", &format!("The run failed: {error:#}")));
290 reporter.flush();
291 let _ = reporter.close();
292 std::process::exit(1);
293 }
294 }
295}

This file's history is long; its oldest lines are credited to the oldest commit read.