Skip to content

Compare changes

Choose two branches to see what one has that the other does not, then open a pull request for it.

Open a pull request

2 commits

10 files+178−950/10 viewed
+9−1
8080 agents have cost. Every member can see it. The sidebar shows this month's
8181 spend.
8282
83+While g1t is free, nothing is charged, so the page and the sidebar show
84+what runs **used**, at what they cost: g1t's models and your own provider's
85+together. Credit is not drawn down.
86+
87+These figures are what the agent harness reports for each run. Your model
88+provider's or gateway's own figures can differ by a few percent, because
89+each prices the same tokens itself; the provider's invoice is what counts.
90+
8391 Pick a period: **This month**, **Last 7 days**, **Last 30 days** or **Last
8492 90 days**. The page then shows:
8593
8694 | | |
8795 | --- | --- |
88−| Spent | What the period cost, and how much of it was the model provider's. |
96+| Spent | What the period cost, and how much of it was the model provider's. **Used**, at cost, while g1t is free. |
8997 | Agent runs | How many runs there were. |
9098 | Average run | What a run cost on average. |
9199 | Credit left | The balance, and about how many days it lasts at the period's rate. |
+4−3
6565 /** Whether g1t charges nothing for now, while it is being built out. */
6666 free?: boolean;
6767 /** What its agents have cost since the start of the month. */
68− monthSpentMicros: number | null;
68+ /** This month's usage: charged, or at cost while g1t is free. */
69+ monthUsageMicros: number | null;
6970 };
7071
7172 function SidebarLink({
176177
177178 /** This month's spend against what is left, as Vercel shows a plan's usage. */
178179 function UsageCard({ slug, shell }: { slug: string; shell: ShellData }) {
179− if (shell.monthSpentMicros == null) return null;
180− const spent = shell.monthSpentMicros;
180+ if (shell.monthUsageMicros == null) return null;
181+ const spent = shell.monthUsageMicros;
181182 const left = shell.creditMicros;
182183 const share = left != null && spent + left > 0 ? Math.min(1, spent / (spent + Math.max(left, 0))) : 0;
183184 return (
+2−1
113113 creditMicros:
114114 account?.ok && account.value.status.enabled && !account.value.status.free ? account.value.balanceMicros : null,
115115 free: account?.ok ? Boolean(account.value.status.free) : false,
116− monthSpentMicros: usage?.ok ? usage.value.spentMicros : null,
116+ // While g1t is free every charge is zero, so usage is shown at cost.
117+ monthUsageMicros: usage?.ok ? (usage.value.free ? usage.value.usedMicros : usage.value.spentMicros) : null,
117118 };
118119 }
119120
+1−1
264264
265265 const workspace = shell?.workspace?.slug;
266266 const first = repos[0];
267− const handedOff = active.length > 0 || (shell?.monthSpentMicros ?? 0) > 0;
267+ const handedOff = active.length > 0 || (shell?.monthUsageMicros ?? 0) > 0;
268268 const steps: Step[] = [
269269 {
270270 done: Boolean(workspace),
+36−19
189189 const { period, since, usage, account } = loaderData;
190190 const base = `/${params.owner}`;
191191 const days = Math.max(1, Math.ceil((Date.now() - new Date(since).getTime()) / 86_400_000));
192− const perDay = usage.spentMicros / days;
192+ // While g1t is free nothing is charged, so usage is measured at cost
193+ // and credit is not drawn down.
194+ const total = usage.free ? usage.usedMicros : usage.spentMicros;
195+ const perDay = usage.free ? 0 : total / days;
193196 const runway = perDay > 0 ? Math.floor(account.balanceMicros / perDay) : null;
194197 return (
195198 <div className="space-y-8">
211214 </div>
212215
213216 <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
214− <Stat
215− label="Spent"
216− value={dollars(usage.spentMicros)}
217− note={
218− usage.providerMicros > 0
219− ? `Plus about ${dollars(usage.providerMicros)} billed by your own model provider`
220− : `${dollars(usage.costMicros)} of it the model provider's`
221− }
222− />
217+ {usage.free ? (
218+ <Stat
219+ label="Used"
220+ value={dollars(total)}
221+ note="At cost. Nothing is charged while g1t is being built out."
222+ />
223+ ) : (
224+ <Stat
225+ label="Spent"
226+ value={dollars(usage.spentMicros)}
227+ note={
228+ usage.providerMicros > 0
229+ ? `Plus about ${dollars(usage.providerMicros)} billed by your own model provider`
230+ : `${dollars(usage.costMicros)} of it the model provider's`
231+ }
232+ />
233+ )}
223234 <Stat label="Agent runs" value={String(usage.runs)} note={PERIODS[period]} />
224235 <Stat
225236 label="Average run"
226− value={usage.runs ? dollars(usage.spentMicros / usage.runs, 3) : "—"}
237+ value={usage.runs ? dollars(total / usage.runs, 3) : "—"}
227238 note="Making a change, reviewing, revising…"
228239 />
229240 <div className="rounded-2xl bg-surface p-5 ring-1 ring-line">
230241 <p className="flex items-center justify-between text-sm text-muted">
231242 Credit left
232− <Link to={`${base}/-/billing`} className="text-xs text-accent hover:underline">
233− Add credit
234− </Link>
243+ {!usage.free && (
244+ <Link to={`${base}/-/billing`} className="text-xs text-accent hover:underline">
245+ Add credit
246+ </Link>
247+ )}
235248 </p>
236249 <p className={`mt-2 text-3xl font-semibold tracking-tight tabular-nums ${account.balanceMicros <= 0 ? "text-warn" : ""}`}>
237250 {dollars(account.balanceMicros)}
238251 </p>
239252 <p className="mt-1 text-xs text-faint">
240− {runway == null ? "Nothing spent in this period." : `About ${runway} ${runway === 1 ? "day" : "days"} at this rate.`}
253+ {usage.free
254+ ? "Not drawn down while g1t is free."
255+ : runway == null
256+ ? "Nothing spent in this period."
257+ : `About ${runway} ${runway === 1 ? "day" : "days"} at this rate.`}
241258 </p>
242259 </div>
243260 </div>
244261
245262 <section className="rounded-2xl bg-surface p-5 ring-1 ring-line">
246263 <div className="flex flex-wrap items-baseline justify-between gap-3">
247− <h3 className="text-sm font-medium">Spend per day</h3>
264+ <h3 className="text-sm font-medium">{usage.free ? "Usage per day" : "Spend per day"}</h3>
248265 <ul className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted">
249266 {usage.byTask.map((slice) => (
250267 <li key={slice.key} className="flex items-center gap-1.5">
263280 <Breakdown
264281 title="By kind of work"
265282 slices={usage.byTask}
266− total={usage.spentMicros}
283+ total={total}
267284 label={(key) => task(key).label}
268285 color={(key) => task(key).color}
269286 />
270− <Breakdown title="By repository" slices={usage.byRepo} total={usage.spentMicros} link={(key) => `/${key}`} />
287+ <Breakdown title="By repository" slices={usage.byRepo} total={total} link={(key) => `/${key}`} />
271288 <Breakdown
272289 title="Pull requests that cost most"
273290 slices={usage.byPull}
280297 return number && number !== "0" ? `/${repo}/pull/${number}` : `/${repo}/plans`;
281298 }}
282299 />
283− <Breakdown title="By model" slices={usage.byModel} total={usage.spentMicros} color={() => "var(--color-info)"} />
300+ <Breakdown title="By model" slices={usage.byModel} total={total} color={() => "var(--color-info)"} />
284301 </div>
285302
286303 <div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl bg-surface px-5 py-4 text-sm ring-1 ring-line">
+6−0
207207 /// What runs on the workspace's own provider cost there, as the harness
208208 /// estimated it. Not charged by g1t.
209209 pub provider_micros: i64,
210+ /// What the runs used, at cost: g1t's models and the workspace's own
211+ /// provider together, whatever was charged for them.
212+ pub used_micros: i64,
213+ /// g1t charges nothing for now. The slices then measure usage at cost,
214+ /// since every charge is zero.
215+ pub free: bool,
210216 pub runs: u32,
211217 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
212218 pub by_day: Vec<UsageSlice>,
+106−67
11 # Demo script
22
3−A walk through g1t for the submission video. It runs seven to eight minutes
4−at a normal speaking pace and uses `syntaqx/hello`, which is seeded for it.
3+A walk through g1t for the submission video. It runs about eight minutes at
4+a normal speaking pace and uses `syntaqx/hello`, a small Rust greeter whose
5+whole history was written by agents working on issues.
56
67 Everything shown is live on g1t.sh. Nothing is mocked.
78
9+The judges weigh agent collaboration (half), concurrency and conflicts (a
10+quarter) and ease of use (a quarter). Sections 3 to 6 carry the first two;
11+sections 2 and 8 the third.
12+
813 ## Before recording
914
1015 - Sign in as `syntaqx`.
1116 - Have a terminal open in an empty directory, with Claude Code installed.
1217 - Open `https://g1t.sh/syntaqx/hello` in one tab and `https://g1t.sh/` in
1318 another.
14−- Agents take one to three minutes. Start them, talk over something else,
15− and come back. Do not deploy the runner while they work.
19+- Write the three issues for section 3 in a scratch file so they can be
20+ pasted (titles below). Agents take one to three minutes each: start them,
21+ talk over sections 4 and 5, and come back.
22+- Do not deploy the runner while agents work.
1623
1724 ## 1. The problem (30 seconds)
1825
2532 > is ordinary git, with issues and pull requests, and it runs entirely on
2633 > Cloudflare.
2734
28−## 2. It is still git (45 seconds)
35+## 2. It is still git, and your CI comes with you (1 minute)
2936
3037 On `syntaqx/hello`, Code tab.
3138
3239 - Show the clone box: HTTPS, and the one line that connects an agent.
3340 - In the terminal: `git clone https://g1t.sh/syntaqx/hello.git`.
34−- Mention: storage is Cloudflare Artifacts; the site, the API and every
35− service are Workers.
41+- Open `.g1t/workflows/ci.yml`. It is a GitHub Actions workflow, unchanged:
42+ `actions/checkout@v7`, a Rust toolchain action, `actions/cache@v6`, then
43+ formatting, lints, an "Every flag is documented" step, and tests.
44+
45+> Moving from GitHub is renaming `.github` to `.g1t`. The same workflow
46+> syntax, the same actions from the marketplace, the same `push`,
47+> `pull_request` and `merge_group` events. Storage is Cloudflare Artifacts;
48+> every job runs in its own Cloudflare Container.
3649
37−## 3. Many issues, an agent on each (2 minutes)
50+- Actions tab: the runs, by event. Open one and show the steps and the log.
51+
52+## 3. Many issues, an agent on each (1 minute 30 seconds)
3853
3954 Issues tab.
4055
41−- Point out labels, and that issues come from people, agents or anything
42− with a token, such as an error tracker.
43−- Tick several open issues and press **Assign to g1t agent**. Say that
44− there is nothing else to choose: no number of agents and no model. Each
45− issue gets an agent of its own and g1t routes the work; every session
46− opens by naming the model that ran.
47−- Open **Document the command-line options**. Its draft pull request has
48− appeared. Show the **Session** tab filling in live: the prompt, the
49− agent's reasoning, every command.
56+- Create three issues quickly, pasting them in:
57+ - **Add a --sparkle flag** that ends the greeting with a sparkle emoji.
58+ - **Greet in German** with `--lang de`.
59+ - **Explain in the README what happens with no name.**
60+- Tick all three and press **Assign to g1t agent**. Say there is nothing
61+ else to choose: no number of agents, no model. Each issue gets an agent of
62+ its own and g1t routes the work; every session opens by naming the model
63+ that ran.
64+- Open one. Its draft pull request has appeared. Show the **Session** tab
65+ filling in live: the prompt, what the agent was told about the other work
66+ in progress, every command it runs.
5067
51−> Each agent has its own sandbox, a Cloudflare Container, and its own fork.
52−> A fork is copy-on-write, so it costs about what a branch would, and an
53−> agent cannot damage what it cannot write to.
68+> Each agent has its own sandbox and its own fork. A fork is copy-on-write,
69+> so it costs about what a branch would, and an agent cannot damage what it
70+> cannot write to. Each is told what else is in flight, so two agents on
71+> the same file know about each other before they collide.
5472
55−While they run, go to the next section.
73+While they run, go on.
5674
57−## 4. Checks nobody can fake (1 minute)
75+## 4. Agents keep CI honest, and fix what it catches (1 minute 30 seconds)
5876
59−Open **Say goodbye too** and its pull request, **Add a farewell**.
77+Open issue **#80, CI: fail when a flag is missing from the README**, and its
78+pull request **#81**.
6079
61−- This one was pushed as a branch by a person, the way you already work.
62−- **Checks failed.** Expand `cargo test` and show the output.
63−- Changes tab: the reviewer's comment sits on the faulty line.
80+- An agent wrote this CI step. Changes tab: the shell step it added to
81+ `ci.yml`. It went through review and the merge queue like any change.
6482
65−> The issue says what done means: here, `cargo test`. g1t runs that itself,
66−> in a clean sandbox that holds only this commit. The agent that wrote the
67−> code never touches the result, so a pass means something. And a pull
68−> request that has not passed cannot be merged.
83+Open issue **#84, Add a --reverse flag**, and its pull request **#85**.
6984
70−## 5. Choosing between pull requests (1 minute 15 seconds)
85+- Its checks, `cargo test`, passed. Its first workflow run did not:
86+ **Formatting** failed. Open the run and show the step and its log.
87+- Session tab: the agent's second session opens with the failed run, the
88+ instruction to read it with `get_workflow_run` and `get_job_logs`, and to
89+ fix the code rather than the workflow. Show it reading the log, fixing
90+ the formatting, and pushing. The second run is green.
7191
72−Open **Greet in Spanish and French**.
92+> Nobody marks their own homework. Workflows run in a clean sandbox on the
93+> exact commit; the agent that wrote the code never touches the result. A
94+> failure goes back to the agent with the log, and the pull request cannot
95+> merge until it is green.
7396
74−- Two pull requests for the same issue, side by side: checks, size of the
75− change, who reviewed.
76−- Open one. Show **Review by a g1t agent**: comments on lines, a summary, a
77− verdict. Say that an agent cannot review its own pull request.
78−- Show **Other work is changing the same files**.
97+## 5. Checks, reviews and choosing between pull requests (1 minute)
7998
80−> This is the overlap radar. Two pull requests for different issues are
81−> editing the same file. g1t says so while the work is still going on, not
82−> at the end as a merge conflict. Agents see the same thing through the API.
99+Open **Say goodbye too** (#4) and its pull request **Add a farewell** (#9).
83100
84−## 6. Converging on main (1 minute 15 seconds)
101+- This one was pushed as a branch by a person, the way you already work.
102+- **Checks failed.** Expand `cargo test` and show the output. Changes tab:
103+ the reviewer's comment sits on the faulty line. The agent's pull request
104+ for the same issue, #10, passed and was merged; #9 was closed.
85105
86−Open **A blank name greets nobody**.
106+Open **Greet in Spanish and French** (#2).
87107
88−- It is closed, and it says which pull request resolved it. The other one
89− is marked superseded.
108+- Two pull requests for one issue, side by side: checks, size of the change,
109+ who reviewed. Open one and show **Other work is changing the same files**.
90110
91−> Two pull requests for one issue, one merged. The issue records which.
111+> This is the overlap radar. g1t says so while the work is still going on,
112+> not at the end as a merge conflict. Agents see the same thing through the
113+> API, which is how the agents in section 3 were told about each other.
92114
93−Go back to a pull request for the Spanish and French issue that says main
94−has moved.
115+Open **A blank name greets nobody** (#1): closed, saying which pull request
116+resolved it; the other is marked superseded.
117+
118+## 6. The merge queue (1 minute 15 seconds)
119+
120+Back to the pull requests from section 3. Their checks have passed and a g1t
121+agent has reviewed them.
122+
123+- With auto-merge on, they enter the **Merge queue** on their own. Open it.
124+
125+> Three changes, written at the same time, each green on its own. That
126+> proves nothing about all three together. The queue builds main with the
127+> first, main with the first and second, and so on, and tests every one of
128+> those combinations at once, in parallel sandboxes: the issues' acceptance
129+> checks, every check main has promised so far, and the repository's
130+> `merge_group` workflows, exactly as GitHub's merge queue sends them.
95131
96−- Press **Catch up with main**. Open the Session tab.
132+- As each lands, the issue closes, recording which pull request resolved it.
133+- Show #79 and #81 under **Recent**: landed, with the `merge_group` run.
97134
98−> Something else landed first, so this pull request is behind. A g1t agent
99−> merges main in. If that conflicts, the agent is given both sides and what
100−> this pull request is for, and resolves it. Then the checks run again on
101−> the result.
135+> When a combination fails, that entry is taken out with the reason, the
136+> ones behind it are tested again without it, and its agent is sent back
137+> to fix it. A conflict with something ahead of it says which.
102138
103−- When it is done, merge it. The issue closes; the other pull request for
104− it closes as superseded.
139+If one conflicts on camera, so much the better: open its Session and show
140+the agent being given both sides and what the pull request is for.
105141
106142 ## 7. Bring your own agent (45 seconds)
107143
114150 - In Claude Code, `/mcp`, choose g1t. The browser opens on g1t's consent
115151 page. Approve.
116152 - Ask: "What issues are open on syntaqx/hello on g1t, and which pull
117− requests overlap?"
153+ requests overlap? Did the last CI run pass?"
118154
119−> No token to paste. The same operations are a REST API at api.g1t.sh, and
120−> the two are generated from one list, so they cannot drift apart.
155+> No token to paste. The same operations are a REST API at api.g1t.sh,
156+> including GitHub's own Actions endpoints, and the two are generated from
157+> one list, so they cannot drift apart.
121158
122159 ## 8. Close (30 seconds)
123160
124−Back on the first issue: the three pull requests from section 3 are ready,
125−with their checks.
161+Back on the Issues tab: the three issues from section 3, closed, each saying
162+which pull request resolved it.
126163
127−> Issues and pull requests, as you know them. What changes is the number of
128−> hands. Every pull request isolated in its own fork, every decision
129−> recorded with the code, checks that agents cannot mark themselves, overlap
130−> flagged while it is happening, and one clear answer to which change you
131−> took. g1t is open source, and it is hosted on itself.
164+> Issues and pull requests, as you know them, and your GitHub Actions as
165+> they are. What changes is the number of hands. Every agent isolated in its
166+> own fork, told what the others are doing, held to checks and workflows it
167+> cannot mark itself, reviewed, and landed through a queue that tests the
168+> combinations. g1t is open source, free while it is being built out, and
169+> hosted on itself.
132170
133171 Show `https://g1t.sh/syntaqx/g1t`.
134172
136174
137175 | What | Do |
138176 | --- | --- |
139−| An agent's pull request closes itself | Open its Session; the last note says why. Assign another. |
177+| An agent's pull request closes itself | Open its Session; the last note says why. Assign the issue again. |
178+| A workflow stays queued | Open the run and press **Re-run all jobs**. |
140179 | Checks stay queued | Press the re-run button on the checks panel. |
141−| Catch up does nothing | The pull request was already up to date; refresh. |
142−| Merge is refused | Read the message: it is a draft, its checks have not passed, or main moved. |
180+| Nothing enters the queue | Auto-merge waits for checks, workflows and a review; the pull request's sidebar says which is missing. |
181+| Merge is refused | Read the message: it is a draft, its checks or workflows have not passed, or it needs a review. |
+4−0
116116 costMicros: number;
117117 /** What runs on the workspace's own provider cost there, estimated. Not charged by g1t. */
118118 providerMicros: number;
119+ /** What the runs used, at cost: g1t's models and the workspace's own provider together. */
120+ usedMicros: number;
121+ /** g1t charges nothing for now; the slices then measure usage at cost. */
122+ free: boolean;
119123 runs: number;
120124 /** Spend per day and task, keyed `YYYY-MM-DD/task`. */
121125 byDay: UsageSlice[];
+5−1
261261 micros: Option<i64>,
262262 runs: Option<u32>,
263263 }
264+ // While nothing is charged, what was used is what there is to show.
265+ let measure = if self.free { "COALESCE(cost_micros, 0)" } else { "-amount_micros" };
264266 let slices = |key: &str, limit: u32| {
265267 format!(
266− "SELECT {key} AS key, -SUM(amount_micros) AS micros, COUNT(*) AS runs FROM ledger
268+ "SELECT {key} AS key, SUM({measure}) AS micros, COUNT(*) AS runs FROM ledger
267269 WHERE workspace = ?1 AND kind = 'usage' AND created_at >= ?2
268270 GROUP BY 1 ORDER BY micros DESC LIMIT {limit}"
269271 )
323325 spent_micros: totals.spent.unwrap_or_default(),
324326 cost_micros: totals.cost.unwrap_or_default(),
325327 provider_micros: totals.provider.unwrap_or_default(),
328+ used_micros: totals.cost.unwrap_or_default() + totals.provider.unwrap_or_default(),
329+ free: self.free,
326330 runs: totals.runs.unwrap_or_default(),
327331 added_micros: totals.added.unwrap_or_default(),
328332 by_day: query(slices("substr(created_at, 1, 10) || '/' || COALESCE(task, 'other')", 400)).await?,
+5−2
3232 # Claude Code refuses to skip permission prompts as root.
3333 USER node
3434 ENV HOME=/home/node
35−# Rust, for the agent's own use, installed for the user it runs as.
35+# Rust, for the agent's own use, installed for the user it runs as, with
36+# the formatter and linter that CI so often checks with: an agent that
37+# cannot run them only finds out from a failed workflow.
3638 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
37− | sh -s -- -y --profile minimal --default-toolchain stable
39+ | sh -s -- -y --profile minimal --default-toolchain stable \
40+ --component rustfmt --component clippy
3841 ENV PATH=/home/node/.cargo/bin:$PATH
3942 # Commits are the g1t agent's; the harness does not sign them as its own.
4043 RUN mkdir -p /home/node/.claude \