Skip to content
285 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.

Workspace names and icons, and a component kit for every control1//! 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
Agents and memory, checks and conflicts, profiles, slug renames, custom domains13//! 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.
Workspace names and icons, and a component kit for every control20//!
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`.
Workspace names and icons, and a component kit for every control35
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look36mod abuse;
Workspace names and icons, and a component kit for every control37mod actions;
Merge branch 'worktree-agent-ac5b181a013e54348'38mod backup;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily39mod bump;
Workspace names and icons, and a component kit for every control40mod checks;
Fast pages, required checks on the branch, self-hosted runners, honest incidents41mod 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.42mod computer;
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step43mod 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.44mod customize;
Workspace names and icons, and a component kit for every control45mod deploy;
Merge branch 'main' into actions-toolkit-oidc-artifacts46mod docker;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API47mod guard;
Workspace names and icons, and a component kit for every control48mod harness;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API49mod learned;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains50mod mergecheck;
Workspace names and icons, and a component kit for every control51mod plan;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains52mod progress;
Workspace names and icons, and a component kit for every control53mod queue;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API54mod reply;
Workspace names and icons, and a component kit for every control55mod report;
56mod review;
57mod revise;
Fast pages, required checks on the branch, self-hosted runners, honest incidents58mod selfhosted;
Workspace names and icons, and a component kit for every control59mod steer;
60mod update;
61
62use std::path::Path;
63use std::process::Command;
64
65use anyhow::{Context, Result, bail};
66use base64::Engine;
67use base64::engine::general_purpose::STANDARD;
68
69use report::{Entry, Reporter};
70
71pub(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-agent72/// The name and address on every commit g1t makes here, whether its agent
73/// or g1t itself: `g1t_contracts::system::{USERNAME, EMAIL}`.
74pub(crate) const AUTHOR_NAME: &str = "g1t";
75pub(crate) const AUTHOR_EMAIL: &str = "g1t@users.noreply.g1t.sh";
Workspace names and icons, and a component kit for every control76
77pub(crate) fn env(name: &str) -> Result<String> {
78 std::env::var(name).with_context(|| format!("{name} is not set"))
79}
80
81/// Runs git and returns its trimmed output, failing on a non-zero exit.
82pub(crate) fn git(dir: &Path, args: &[&str]) -> Result<String> {
83 let output = Command::new("git")
84 .current_dir(dir)
85 .args(args)
86 .output()
87 .context("could not run git")?;
88 if !output.status.success() {
89 bail!(
90 "git {} failed: {}",
91 args.first().unwrap_or(&""),
92 String::from_utf8_lossy(&output.stderr).trim()
93 );
94 }
95 Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned())
96}
97
98/// A git option that authenticates one command. The credential is passed
99/// per command and never written to the clone's config or its remote URL,
100/// where the agent would find it.
101pub(crate) fn auth_option(user: &str, token: &str) -> String {
102 let credentials = STANDARD.encode(format!("{user}:{token}"));
103 format!("http.extraHeader=Authorization: Basic {credentials}")
104}
105
106/// Clones the fork, runs the agent on `PROMPT`, commits and pushes what it
107/// did, and returns its closing summary.
108pub(crate) fn run(reporter: &mut Reporter) -> Result<String> {
109 let mut prompt = env("PROMPT")?;
110 let remote = env("GIT_REMOTE")?;
111 let auth = auth_option(&env("G1T_USER")?, &env("G1T_TOKEN")?);
112 let workdir = Path::new(WORKDIR);
113
Merge branch 'model-routing'114 // Which model, and why: the router's one line names both.
115 let reason = std::env::var("AGENT_MODEL_REASON").unwrap_or_default();
116 if !reason.trim().is_empty() {
117 reporter.record(Entry::new("note", reason.trim()));
118 } else if let Ok(model) = std::env::var("AGENT_MODEL_NAME") {
Workspace names and icons, and a component kit for every control119 reporter.record(Entry::new("note", &format!("Running on {model}.")));
120 }
121 reporter.record(Entry::new("prompt", &prompt));
122 reporter.flush();
123
124 std::fs::create_dir_all("/work")?;
Fast pages, required checks on the branch, self-hosted runners, honest incidents125 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-agent126 git(workdir, &["config", "user.name", crate::AUTHOR_NAME])?;
127 git(workdir, &["config", "user.email", crate::AUTHOR_EMAIL])?;
Workspace names and icons, and a component kit for every control128 let branch = git(workdir, &["rev-parse", "--abbrev-ref", "HEAD"])?;
129 let start = git(workdir, &["rev-parse", "HEAD"]).unwrap_or_default();
130
131 // Sent back to work that is already open: start from where the branch it
132 // will land on is now, so what passes here passes there too.
133 if let (Ok(upstream), Ok(upstream_branch)) = (env("UPSTREAM_REMOTE"), env("UPSTREAM_BRANCH")) {
Fast pages, required checks on the branch, self-hosted runners, honest incidents134 clone::fetch(workdir, &auth, &upstream, &upstream_branch).context("could not fetch the branch this will land on")?;
135 // Shallow: deep enough to tell whether it is behind, and to merge.
136 clone::share_history(workdir, &auth, &[("origin", branch.as_str()), (upstream.as_str(), upstream_branch.as_str())], "HEAD", "FETCH_HEAD")?;
Workspace names and icons, and a component kit for every control137 let behind = Command::new("git")
138 .current_dir(workdir)
139 .args(["merge-base", "--is-ancestor", "FETCH_HEAD", "HEAD"])
140 .status()
141 .is_ok_and(|status| !status.success());
142 if behind {
143 let message = format!("Catch up with {upstream_branch}");
144 let merged = Command::new("git")
145 .current_dir(workdir)
146 .args(["merge", "--quiet", "--no-edit", "-m", &message, "FETCH_HEAD"])
147 .status()
148 .is_ok_and(|status| status.success());
149 if merged {
150 reporter.record(Entry::new(
151 "note",
152 &format!("Merged in the latest {upstream_branch} before starting."),
153 ));
154 } else {
155 let files = git(workdir, &["diff", "--name-only", "--diff-filter=U"])?;
156 let files: Vec<&str> = files.lines().collect();
157 reporter.record(Entry::new(
158 "note",
159 &format!(
160 "Merged in the latest {upstream_branch} before starting; {} conflict.",
161 files.join(", ")
162 ),
163 ));
164 prompt.push_str(&format!(
165 "\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.",
166 files.join(", ")
167 ));
168 }
169 reporter.flush();
170 }
171 }
172
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step173 // The agent is asked what it learned, which goes to memory, and how sure
174 // it is of its change, which g1t weighs with what it observes. Neither
175 // stays in the summary.
176 let asked = confidence::ask(&learned::ask(&prompt));
177 let summary = confidence::finish(learned::finish(harness::run_claude(workdir, &asked, reporter)?));
Workspace names and icons, and a component kit for every control178
179 // Commit whatever the agent left in the working tree.
180 if !git(workdir, &["status", "--porcelain"])?.is_empty() {
181 let message = std::env::var("COMMIT_MESSAGE").unwrap_or_else(|_| "Agent changes".into());
182 git(workdir, &["add", "--all"])?;
183 git(workdir, &["commit", "--quiet", "--message", &message])?;
184 }
185 let head = git(workdir, &["rev-parse", "HEAD"])?;
186 if head == start {
187 // An agent woken to answer usually only answers.
188 if std::env::var("MODE").as_deref() == Ok("answer") {
189 return Ok(summary);
190 }
191 bail!("the agent finished without changing anything");
192 }
193 git(
194 workdir,
195 &[
196 "-c",
197 &auth,
198 "push",
199 "--quiet",
200 "origin",
201 &format!("HEAD:{branch}"),
202 ],
203 )
204 .context("could not push the pull request's commits")?;
205 reporter.record(Entry::new(
206 "note",
207 &format!("Pushed {}.", &head[..head.len().min(12)]),
208 ));
209 Ok(summary)
210}
211
212fn main() {
Merge branch 'main' into actions-toolkit-oidc-artifacts213 // The runc the job's Docker Engine starts containers with (docker/oci.rs).
214 if docker::oci::invoked_as_runc() {
215 docker::oci::main();
216 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents217 // A self-hosted runner's commands; the modes below are what it, and
218 // g1t's sandboxes, run work with.
219 let args: Vec<String> = std::env::args().skip(1).collect();
220 if selfhosted::is_command(&args) {
221 std::process::exit(selfhosted::main(args));
222 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API223 // A guarded sandbox's HTTPS is re-signed on its way out: trust that
224 // before anything is fetched. The guard hook runs before every tool
225 // call, so it skips this.
226 if std::env::var("MODE").as_deref() == Ok("guard") {
227 std::process::exit(guard::hook_main());
228 }
229 guard::trust_egress_ca();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look230 // Watches for mining for as long as the sandbox runs (abuse.rs). Not in
231 // the hooks the harness runs after every tool call.
232 if std::env::var("MODE").as_deref() != Ok("steer") {
233 abuse::watch();
234 }
Workspace names and icons, and a component kit for every control235 // The same image does the other jobs a sandbox is started for.
236 match std::env::var("MODE").as_deref() {
237 Ok("actions") => std::process::exit(actions::main()),
Merge branch 'worktree-agent-ac5b181a013e54348'238 Ok("backup") => std::process::exit(backup::main()),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily239 Ok("bump") => std::process::exit(bump::main()),
Workspace names and icons, and a component kit for every control240 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.241 // An agent's own computer: serves until it is put to sleep.
242 Ok("computer") => std::process::exit(computer::main()),
Workspace names and icons, and a component kit for every control243 Ok("deploy") => std::process::exit(deploy::main()),
244 Ok("update") => std::process::exit(update::main()),
245 Ok("review") => std::process::exit(review::main()),
246 Ok("revise") => std::process::exit(revise::main()),
247 Ok("answer") => std::process::exit(revise::answer()),
248 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 API249 Ok("reply") => std::process::exit(reply::main()),
Workspace names and icons, and a component kit for every control250 Ok("queue") => std::process::exit(queue::main()),
Agents and memory, checks and conflicts, profiles, slug renames, custom domains251 Ok("mergecheck") => std::process::exit(mergecheck::main()),
Workspace names and icons, and a component kit for every control252 Ok("steer") => std::process::exit(steer::main()),
253 _ => {}
254 }
255 let mut reporter = match Reporter::from_env() {
256 Ok(reporter) => reporter,
257 Err(error) => {
258 eprintln!("g1t-runner: {error:#}");
259 std::process::exit(2);
260 }
261 };
262 match run(&mut reporter) {
263 Ok(summary) => {
264 reporter.flush();
265 if let Err(error) = reporter.ready(&summary) {
266 eprintln!("g1t-runner: could not mark the pull request ready: {error:#}");
267 std::process::exit(1);
268 }
269 }
270 Err(error) => {
271 eprintln!("g1t-runner: {error:#}");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API272 // Stopped at a cap: like a person's stop, the pull request is
273 // left open for a person, not closed.
274 if guard::is_halt(&error) {
275 reporter.record(Entry::new("note", &format!("g1t stopped the agent: {error:#}.")));
276 reporter.flush();
277 std::process::exit(1);
278 }
Workspace names and icons, and a component kit for every control279 reporter.record(Entry::new("note", &format!("The run failed: {error:#}")));
280 reporter.flush();
281 let _ = reporter.close();
282 std::process::exit(1);
283 }
284 }
285}

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