Skip to content
424 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 Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.1import { ArrowRight, Bot, Boxes, Cloud, Cpu, Laptop, Plus, ServerCog, Workflow } from "lucide-react";
2import type { ReactNode } from "react";
3import { Link } from "react-router";
4
5import { RUN_KIND_LABEL, type Runner, type RunKind } from "@g1t/contracts";
6
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.7import { money } from "../lib/money";
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.8import { type CloudWork, machineTime, perMinute } from "../lib/runners";
9import type { RunnersAction, RunnersPageData } from "../lib/runners.server";
10import { Groups, NewRunner, RunnerRow, RunnerSettingsForm, STATUS } from "./runners";
11import { EmptyState, ErrorText, TimeAgo } from "./ui";
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.12import { Badge } from "./ui/badge";
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.13import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
14
15/** The most of what runs on g1t's cloud the card lists before saying how many more. */
16const CLOUD_SHOWN = 6;
17
18/** A small label that something is coming. */
19function Coming() {
20 return <span className="shrink-0 rounded-full px-1.5 py-px text-[0.625rem] font-medium tracking-wide text-muted uppercase ring-1 ring-line">Coming</span>;
21}
22
23function Section({ id, title, about, action, children }: { id?: string; title: string; about?: ReactNode; action?: ReactNode; children: ReactNode }) {
24 return (
25 <section id={id} className="scroll-mt-6 space-y-3">
26 <div className="flex flex-wrap items-end justify-between gap-x-4 gap-y-1">
27 <div className="min-w-0">
28 <h2 className="text-base font-semibold tracking-tight">{title}</h2>
29 {about && <div className="mt-0.5 text-sm text-muted">{about}</div>}
30 </div>
31 {action}
32 </div>
33 {children}
34 </section>
35 );
36}
37
38/** One figure in a place's card: a label and its value. */
39function Figure({ label, hint, children }: { label: string; hint?: string; children: ReactNode }) {
40 return (
41 <div className="flex items-baseline justify-between gap-3 py-2 text-sm">
42 <dt className="text-muted">
43 {hint ? (
44 <Tooltip>
45 <TooltipTrigger asChild>
46 <span className="cursor-help underline decoration-line-strong decoration-dotted underline-offset-4">{label}</span>
47 </TooltipTrigger>
48 <TooltipContent>{hint}</TooltipContent>
49 </Tooltip>
50 ) : (
51 label
52 )}
53 </dt>
54 <dd className="min-w-0 text-right text-fg tabular-nums">{children}</dd>
55 </div>
56 );
57}
58
59function Unknown({ what }: { what: string }) {
60 return <span className="text-faint">{what} didn&apos;t answer</span>;
61}
62
63function PlaceCard({ icon, title, tag, about, headline, children }: { icon: ReactNode; title: string; tag: ReactNode; about: string; headline: ReactNode; children: ReactNode }) {
64 return (
65 <section className="flex min-w-0 flex-col rounded-2xl border border-line bg-surface">
66 <header className="px-5 pt-4">
67 <div className="flex items-center gap-2">
68 <span className="grid size-7 shrink-0 place-items-center rounded-lg bg-raised text-fg-soft">{icon}</span>
69 <h2 className="text-sm font-semibold">{title}</h2>
70 <span className="ml-auto">{tag}</span>
71 </div>
72 <p className="mt-2 text-sm leading-relaxed text-muted">{about}</p>
73 </header>
74 <div className="px-5 pt-4">{headline}</div>
75 <dl className="mt-2 divide-y divide-line border-t border-line px-5 py-1">{children}</dl>
76 </section>
77 );
78}
79
80function kindLabel(work: CloudWork): string {
81 if (work.kind === "workflow") return "Workflow job";
82 return RUN_KIND_LABEL[work.detail as RunKind] ?? "Agent run";
83}
84
85function CloudList({ data }: { data: RunnersPageData }) {
86 const cloud = data.cloud;
87 if (!cloud) return <p className="px-5 py-4 text-sm text-muted">What runs on g1t&apos;s cloud can&apos;t be read right now.</p>;
88 if (cloud.running.length === 0) {
89 return (
90 <p className="px-5 py-4 text-sm text-muted">
91 Nothing is running here now. A sandbox starts for each agent run or job and stops when it ends{cloud.queued > 0 ? `; ${cloud.queued} waiting to start` : ""}.
92 </p>
93 );
94 }
95 const shown = cloud.running.slice(0, CLOUD_SHOWN);
96 const rest = cloud.running.length - shown.length;
97 return (
98 <ul className="divide-y divide-line">
99 {shown.map((work) => (
100 <li key={`${work.kind}:${work.id}`} className="flex items-center gap-3 px-5 py-2.5">
101 <span className="grid size-6 shrink-0 place-items-center rounded-md bg-info/12 text-info" aria-hidden>
102 {work.kind === "agent" ? <Bot size={13} /> : <Workflow size={13} />}
103 </span>
104 <div className="min-w-0 grow">
105 <Link to={work.href} className="block truncate text-sm text-fg hover:underline">
106 {work.title}
107 </Link>
108 <p className="truncate text-xs text-muted">
109 {kindLabel(work)} · {work.repo}
110 </p>
111 </div>
112 {work.startedAt && (
113 <span className="shrink-0 text-xs text-faint">
114 <TimeAgo at={work.startedAt} />
115 </span>
116 )}
117 </li>
118 ))}
119 {(rest > 0 || cloud.more || cloud.queued > 0) && (
120 <li className="px-5 py-2.5 text-xs text-muted">
121 {[rest > 0 || cloud.more ? `${rest > 0 ? rest : "More"}${cloud.more ? "+" : ""} more running` : null, cloud.queued > 0 ? `${cloud.queued} waiting to start` : null]
122 .filter(Boolean)
123 .join(" · ")}
124 </li>
125 )}
126 </ul>
127 );
128}
129
130function StatusCounts({ runners }: { runners: Runner[] }) {
131 const counts = { busy: 0, online: 0, offline: 0 } as Record<Runner["status"], number>;
132 for (const runner of runners) counts[runner.status] += 1;
133 return (
134 <ul className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted">
135 {(["busy", "online", "offline"] as const).map((status) => (
136 <li key={status} className="flex items-center gap-1.5">
137 <span className={`size-2 rounded-full ${STATUS[status].dot}`} aria-hidden />
138 <span className="text-fg tabular-nums">{counts[status]}</span> {STATUS[status].label.toLowerCase()}
139 </li>
140 ))}
141 </ul>
142 );
143}
144
145/** The workspace's Runners page: g1t's cloud and its own runners, side by side. */
146export function RunnersPage({ data, action, slug }: { data: RunnersPageData; action: RunnersAction | undefined; slug: string }) {
147 const own = data.runners;
148 const online = own.filter((runner) => runner.status !== "offline").length;
149 const cost = data.cost;
150 const running = data.cloud?.running.length ?? null;
151 const usage = `/${slug}/-/usage`;
152 return (
153 <div className="space-y-10">
154 <header className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
155 <div className="min-w-0">
156 <h1 className="text-[1.75rem] leading-tight font-semibold tracking-tight">Runners</h1>
157 <p className="mt-1.5 max-w-2xl text-sm leading-relaxed text-muted">
158 The machines your agents and workflow jobs run on. g1t&apos;s cloud needs nothing set up; your own runners are any machine with{" "}
159 <code className="font-mono text-fg-soft">g1t-runner</code> on it, and time on them is free.
160 </p>
161 </div>
162 <a
163 href="#add"
164 className="inline-flex h-9 shrink-0 items-center gap-1.5 self-start rounded-lg bg-accent px-3.5 text-sm font-medium text-bg transition-colors hover:bg-accent-hover"
165 >
166 <Plus size={15} />
167 Add a runner
168 </a>
169 </header>
170
171 {(action?.notice || action?.error || data.error) && (
172 <div>
173 {action?.notice && <p className="text-sm text-success">{action.notice}</p>}
174 <ErrorText>{action?.error ?? data.error ?? null}</ErrorText>
175 </div>
176 )}
177
178 <div className="grid items-start gap-5 lg:grid-cols-2">
179 <PlaceCard
180 icon={<Cloud size={15} />}
181 title="g1t cloud"
182 tag={<span className="rounded-full bg-raised px-2 py-0.5 text-xs text-fg-soft">Managed by g1t</span>}
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.183 about="A fresh sandbox for every agent run and workflow job, stopped when it ends; workspace agents keep a computer that sleeps when idle. Billed by the second at the price below."
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.184 headline={
185 <div className="flex items-baseline gap-2">
186 <span className="text-3xl font-semibold tracking-tight tabular-nums">{running ?? "–"}</span>
187 <span className="text-sm text-muted">running now</span>
188 </div>
189 }
190 >
191 <Figure label="This month" hint="Agent sandbox time and sandbox time on your usage, at price, from the first of the month.">
192 {cost ? (
193 <>
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.194 {machineTime(cost.cloudSeconds)} · {money(cost.cloudMicros)}
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.195 </>
196 ) : (
197 <Unknown what="Billing" />
198 )}
199 </Figure>
200 {data.prices ? (
201 <Figure label="Price" hint={`From g1t's price book: ${data.prices.map((price) => price.title).join("; ")}.`}>
202 <span className="flex flex-wrap justify-end gap-x-1">
203 {data.prices.map((price, at) => (
204 <span key={price.meter} className="whitespace-nowrap">
205 {at > 0 && "+ "}
206 {perMinute(price.perMinuteMicros)}/{price.unit}
207 </span>
208 ))}
209 </span>
210 </Figure>
211 ) : (
212 <Figure label="Price">
213 <Unknown what="Billing" />
214 </Figure>
215 )}
216 {data.agentCap != null && (
217 <Figure label="Agent runs at once" hint="Your plan's cap on agent runs at the same time.">
218 up to {data.agentCap}
219 </Figure>
220 )}
221 </PlaceCard>
222
223 <PlaceCard
224 icon={<ServerCog size={15} />}
225 title="Your runners"
226 tag={<span className="rounded-full bg-success/12 px-2 py-0.5 text-xs font-medium text-success">Free</span>}
227 about="Your own machines: Linux, macOS or Windows, anywhere they can reach g1t. They only connect out, and take the work whose labels they have."
228 headline={
229 <div className="space-y-2">
230 <div className="flex items-baseline gap-2">
231 <span className="text-3xl font-semibold tracking-tight tabular-nums">{own.length === 0 ? 0 : online}</span>
232 <span className="text-sm text-muted">{own.length === 0 ? "runners yet" : `of ${own.length} online`}</span>
233 </div>
234 {own.length > 0 && <StatusCounts runners={own} />}
235 </div>
236 }
237 >
238 <Figure label="This month" hint="Self-hosted runner time on your usage, from the first of the month. It is never charged.">
239 {cost ? (
240 <>
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.241 {machineTime(cost.ownSeconds)} · {money(0)}
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.242 </>
243 ) : (
244 <Unknown what="Billing" />
245 )}
246 </Figure>
247 <Figure label="Waiting for one of them">
248 {data.waiting ? (
249 data.waiting.jobs + data.waiting.tasks === 0 ? (
250 "Nothing"
251 ) : (
252 [
253 data.waiting.jobs > 0 ? `${data.waiting.jobs} ${data.waiting.jobs === 1 ? "job" : "jobs"}` : null,
254 data.waiting.tasks > 0 ? `${data.waiting.tasks} agent ${data.waiting.tasks === 1 ? "run" : "runs"}` : null,
255 ]
256 .filter(Boolean)
257 .join(", ")
258 )
259 ) : (
260 <Unknown what="Actions" />
261 )}
262 </Figure>
263 <Figure label="Takes agent work">
264 {data.settings ? (data.settings.agentsOnSelfHosted ? `Yes, labelled ${data.settings.agentLabels.join(", ")}` : "No") : "–"}
265 </Figure>
266 </PlaceCard>
267 </div>
268
269 <div className="grid items-start gap-5 lg:grid-cols-2">
270 <Section title="On g1t cloud now" about="Agent runs and workflow jobs in g1t's sandboxes.">
271 <div className="rounded-2xl border border-line bg-surface">
272 <CloudList data={data} />
273 </div>
274 </Section>
275 <Section
276 title="Machine time"
277 about={cost ? `${cost.from} to ${cost.until}, as your usage shows it.` : "From your usage."}
278 action={
279 <Link to={usage} className="inline-flex items-center gap-1 text-xs text-muted hover:text-fg">
280 Usage
281 <ArrowRight size={12} />
282 </Link>
283 }
284 >
285 <TimeSplit data={data} />
286 </Section>
287 </div>
288
289 <Section title="Your runners" about={own.length === 0 ? "None yet." : `${online} of ${own.length} online. A runner that hasn't been heard from for 90 seconds is offline.`}>
290 {own.length === 0 ? (
291 <EmptyState title="No runners of your own yet">
292 <a href="#add" className="underline underline-offset-2">
293 Add one
294 </a>{" "}
295 to run jobs and agents on your hardware. It connects out to g1t; nothing needs to reach the machine.
296 </EmptyState>
297 ) : (
298 <ul className="divide-y divide-line rounded-2xl border border-line bg-surface">
299 {own.map((runner) => (
300 <RunnerRow key={runner.id} runner={runner} manage />
301 ))}
302 </ul>
303 )}
304 </Section>
305
306 <Section title="Where work runs" about="For every project in the workspace, unless a project says otherwise in its own settings.">
307 <div className="grid gap-3 md:grid-cols-3">
308 <WhereCard icon={<Workflow size={15} />} title="Workflow jobs">
309 Each job&apos;s <code className="font-mono text-fg-soft">runs-on</code> decides. <code className="font-mono text-fg-soft">ubuntu-latest</code>,{" "}
310 <code className="font-mono text-fg-soft">g1t-4core</code> and other Linux labels run on g1t cloud;{" "}
311 <code className="font-mono text-fg-soft">self-hosted</code> and your own labels run on your runners.
312 </WhereCard>
313 <WhereCard icon={<Bot size={15} />} title="Agents">
314 {data.settings?.agentsOnSelfHosted
315 ? `Agent runs, checks, reviews and the merge queue run on your runners labelled ${data.settings.agentLabels.join(", ")}.`
316 : "Agent runs, checks, reviews and the merge queue run on g1t cloud. Switch them to your runners below."}
317 </WhereCard>
318 <WhereCard icon={<Laptop size={15} />} title="Your desktop" coming>
319 The desktop app will include a runner: for a logged-in browser, a simulator or a tool only your network reaches. It asks before each task.
320 </WhereCard>
321 </div>
322 <RunnerSettingsForm
323 key={data.settings ? `${data.settings.agentsOnSelfHosted}:${data.settings.forkPullRequests}:${data.settings.agentLabels.join(",")}` : "none"}
324 data={data}
325 manage
326 scope="workspace"
327 />
328 </Section>
329
330 <Section
331 id="add"
332 title="Add a runner"
333 about="Download g1t-runner, register it with a token made here, and start it. Linux, macOS and Windows, x64 and arm64, or Docker."
334 >
335 <NewRunner token={action?.token ?? null} groups={data.groups} repoScoped={false} />
336 </Section>
337
338 <Section title="Groups" about="Which repositories may use which runners. A runner joins the default group, every repository, unless its token names another.">
339 <Groups groups={data.groups} repositories={data.repositories} manage action={action} />
340 </Section>
341
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.342 <Section title="Agents' computers and more">
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.343 <ul className="divide-y divide-line rounded-2xl border border-line bg-surface">
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.344 <ComingRow icon={<Cpu size={15} />} title="Sessions that persist" live>
345 Each workspace agent has a computer of its own on g1t cloud: its home, clones, caches and notes stay between sessions, and it sleeps when idle. Its page is the
346 agent&apos;s Computer tab. Pinning it to one of your runners is coming.
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.347 </ComingRow>
348 <ComingRow icon={<Boxes size={15} />} title="An official agent image">
349 One image with git, Node and the agent&apos;s tools, so agent work runs on any runner with Docker without an image of your own.
350 </ComingRow>
351 </ul>
352 </Section>
353 </div>
354 );
355}
356
357function WhereCard({ icon, title, coming, children }: { icon: ReactNode; title: string; coming?: boolean; children: ReactNode }) {
358 return (
359 <div className={`rounded-2xl border p-4 ${coming ? "border-dashed border-line" : "border-line bg-surface"}`}>
360 <h3 className="flex items-center gap-2 text-sm font-medium">
361 <span className="text-muted">{icon}</span>
362 {title}
363 {coming && <span className="ml-auto"><Coming /></span>}
364 </h3>
365 <p className="mt-2 text-sm leading-relaxed text-muted">{children}</p>
366 </div>
367 );
368}
369
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.370/** One thing runners do or will do: live, or marked Coming. */
371function ComingRow({ icon, title, live = false, children }: { icon: ReactNode; title: string; live?: boolean; children: ReactNode }) {
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.372 return (
373 <li className="flex items-start gap-3 px-4 py-3">
374 <span className="mt-0.5 text-muted">{icon}</span>
375 <div className="min-w-0 grow">
376 <p className="flex items-center gap-2 text-sm font-medium">
377 {title}
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.378 {live ? <Badge tone="success">Live</Badge> : <Coming />}
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.379 </p>
380 <p className="mt-0.5 text-sm text-muted">{children}</p>
381 </div>
382 </li>
383 );
384}
385
386/** This month's machine time, g1t cloud's against the workspace's own, as one bar. */
387function TimeSplit({ data }: { data: RunnersPageData }) {
388 const cost = data.cost;
389 if (!cost) {
390 return (
391 <div className="rounded-2xl border border-line bg-surface px-5 py-4 text-sm text-muted">Billing didn&apos;t answer, so this month&apos;s time can&apos;t be shown right now.</div>
392 );
393 }
394 const total = cost.cloudSeconds + cost.ownSeconds;
395 return (
396 <div className="rounded-2xl border border-line bg-surface px-5 py-4">
397 {total === 0 ? (
398 <p className="text-sm text-muted">No machine time yet this month.</p>
399 ) : (
400 <div className="flex h-2.5 gap-[2px] overflow-hidden rounded-full" role="img" aria-label="This month's machine time by where it ran">
401 {cost.cloudSeconds > 0 && <span className="h-full min-w-[3px] bg-info" style={{ flexGrow: cost.cloudSeconds }} />}
402 {cost.ownSeconds > 0 && <span className="h-full min-w-[3px] bg-success" style={{ flexGrow: cost.ownSeconds }} />}
403 </div>
404 )}
405 <ul className="mt-3 space-y-2 text-sm">
406 <li className="flex items-center gap-2">
407 <span className="size-2 shrink-0 rounded-[2px] bg-info" aria-hidden />
408 <span className="min-w-0 grow truncate text-fg-soft">g1t cloud</span>
409 <span className="text-muted tabular-nums">{machineTime(cost.cloudSeconds)}</span>
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.410 <span className="w-20 text-right text-fg tabular-nums">{money(cost.cloudMicros)}</span>
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.411 </li>
412 <li className="flex items-center gap-2">
413 <span className="size-2 shrink-0 rounded-[2px] bg-success" aria-hidden />
414 <span className="min-w-0 grow truncate text-fg-soft">Your runners</span>
415 <span className="text-muted tabular-nums">{machineTime(cost.ownSeconds)}</span>
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.416 <span className="w-20 text-right text-fg tabular-nums">{money(0)}</span>
The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how.417 </li>
418 </ul>
419 <p className="mt-4 text-[0.6875rem] leading-relaxed text-faint">
420 {cost.free ? "Usage at cost: g1t charges nothing for it now." : "At price, before your plan's included usage or credit paid for any of it."} Models are separate.
421 </p>
422 </div>
423 );
424}