Free while g1t is being built out; agents can check out their own forks
Billing gains FREE_WHILE_BUILDING (on): runs are still recorded with what they cost, so Usage works, but nothing is charged and no credit is needed. The app, the docs and llms.txt say so, as for now and not forever. Workflows run in every workspace that uses g1t's agents, including with only its own model provider. A pull request's workflow runs now get their token and secrets when its author is a member (asked of the workspace, since stored authors carry no memberships) or the repository is private, so actions/checkout can fetch an agent's fork. An agent sent back for failed workflows is told which repository to read the runs in.
17 files+114−330/17 viewed
| 537 | 537 | "A workspace's integrations: its own model provider, the alert sources that open issues (Sentry, Datadog, webhooks), and the trackers whose tickets agents can read (Jira, Linear). Secrets are never returned. Members only." | |
| 538 | 538 | } | |
| 539 | 539 | Op::ConnectIntegration => { | |
| 540 | − | "Connect a workspace to an outside system. provider is a model provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint: your own key, billed by that provider, with g1t charging a flat orchestration fee per run; a workspace can connect several and route each kind of work with set_model_routes), or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only." | |
| 540 | + | "Connect a workspace to an outside system. provider is a model provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint: your own key, billed by that provider, and free on g1t while it is being built out; a workspace can connect several and route each kind of work with set_model_routes), or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only." | |
| 541 | 541 | } | |
| 542 | 542 | Op::DisconnectIntegration => { | |
| 543 | 543 | "Remove an integration and its secrets. Agents already running on a model provider being removed stop reaching it. Owners only." |
| 114 | 114 | ||
| 115 | 115 | ## Who may run workflows | |
| 116 | 116 | ||
| 117 | − | While g1t is in preview, workflows run in the workspaces g1t has opened | |
| 118 | − | them to. Elsewhere a run is recorded with its jobs failed and the reason. | |
| 117 | + | Workflows run in every workspace that uses g1t's agents: one with its own | |
| 118 | + | [model provider](/guides/models/) connected, or one g1t has opened its | |
| 119 | + | hosted models to. They are free while g1t is being built out. Elsewhere a | |
| 120 | + | run is recorded with its jobs failed and the reason. | |
| 119 | 121 | ||
| 120 | 122 | ## From the API | |
| 121 | 123 |
| 205 | 205 | ||
| 206 | 206 | ## What it costs | |
| 207 | 207 | ||
| 208 | − | A workspace pays for the g1t agents that work on its repositories, from | |
| 209 | − | credit an owner buys in advance: each run is charged what the model cost, | |
| 210 | − | plus 20%. Acceptance checks are free. With no credit, agents do not start. | |
| 208 | + | While g1t is being built out, its agents cost nothing: runs are recorded | |
| 209 | + | with what they cost, and nothing is charged. Once pricing starts, a | |
| 210 | + | workspace will pay for the g1t agents that work on its repositories from | |
| 211 | + | credit an owner buys in advance: each run charged what the model cost, plus | |
| 212 | + | 20%, and acceptance checks free. | |
| 211 | 213 | The workspace's **Usage** page shows what its agents have cost, by day, | |
| 212 | 214 | kind of work, repository, model and pull request. See | |
| 213 | 215 | [usage and billing](/guides/usage-and-billing/). |
| 83 | 83 | ||
| 84 | 84 | ## What it costs | |
| 85 | 85 | ||
| 86 | − | On your own providers, they bill you for the models, and g1t charges your | |
| 87 | − | credit a flat **$0.10 per run** for the sandbox and orchestration. A | |
| 86 | + | While g1t is being built out, g1t charges nothing for runs on your own | |
| 87 | + | providers: they bill you for the models, and that is all. Once pricing | |
| 88 | + | starts, g1t will charge your credit a flat **$0.10 per run** for the | |
| 89 | + | sandbox and orchestration. A | |
| 88 | 90 | change, a review, a revision, a catch-up and a plan are each a run. The | |
| 89 | 91 | statement marks these runs "on your own model provider" and names the | |
| 90 | 92 | model and provider; the Usage page shows what they cost at the provider, |
| 3 | 3 | description: What g1t agents cost, how a workspace pays for them, and what is free. | |
| 4 | 4 | --- | |
| 5 | 5 | ||
| 6 | + | > **Free while g1t is being built out.** For now, using g1t costs nothing: | |
| 7 | + | > agents, reviews, checks and workflows. Bring your own model provider and | |
| 8 | + | > its usage is billed by that provider, not by g1t. Runs are still recorded | |
| 9 | + | > with what they cost, so the Usage page shows what you are using. This is | |
| 10 | + | > for now, not forever: the pricing below is how g1t will charge once it | |
| 11 | + | > starts, and we will say so well before anything is charged. | |
| 12 | + | ||
| 6 | 13 | Hosting repositories, issues, pull requests, review and your own agent cost | |
| 7 | 14 | nothing on g1t. What costs money is g1t's own agents: each run uses a | |
| 8 | 15 | model, and a workspace pays for the runs on its repositories from credit it |
| 63 | 63 | } | null; | |
| 64 | 64 | /** The workspace's agent credit, if billing is on and they may see it. */ | |
| 65 | 65 | creditMicros: number | null; | |
| 66 | + | /** Whether g1t charges nothing for now, while it is being built out. */ | |
| 67 | + | free?: boolean; | |
| 66 | 68 | /** What its agents have cost since the start of the month. */ | |
| 67 | 69 | monthSpentMicros: number | null; | |
| 68 | 70 | }; | |
| 190 | 192 | </span> | |
| 191 | 193 | <span className="mt-2 flex items-baseline justify-between"> | |
| 192 | 194 | <span className="font-mono text-sm tabular-nums">${(spent / MICROS_PER_DOLLAR).toFixed(2)}</span> | |
| 193 | − | {left != null && ( | |
| 194 | − | <span className={`text-xs ${left <= 0 ? "text-warn" : "text-faint"}`}> | |
| 195 | − | ${(left / MICROS_PER_DOLLAR).toFixed(2)} left | |
| 196 | − | </span> | |
| 195 | + | {shell.free ? ( | |
| 196 | + | <span className="text-xs text-accent">Free for now</span> | |
| 197 | + | ) : ( | |
| 198 | + | left != null && ( | |
| 199 | + | <span className={`text-xs ${left <= 0 ? "text-warn" : "text-faint"}`}> | |
| 200 | + | ${(left / MICROS_PER_DOLLAR).toFixed(2)} left | |
| 201 | + | </span> | |
| 202 | + | ) | |
| 197 | 203 | )} | |
| 198 | 204 | </span> | |
| 199 | 205 | <span className="mt-2 block h-1 overflow-hidden rounded-full bg-raised"> |
| 109 | 109 | pulls: counts.value.pulls, | |
| 110 | 110 | } | |
| 111 | 111 | : null, | |
| 112 | + | // While g1t is being built out nothing is charged, so no credit is shown. | |
| 112 | 113 | creditMicros: | |
| 113 | − | account?.ok && account.value.status.enabled ? account.value.balanceMicros : null, | |
| 114 | + | account?.ok && account.value.status.enabled && !account.value.status.free ? account.value.balanceMicros : null, | |
| 115 | + | free: account?.ok ? Boolean(account.value.status.free) : false, | |
| 114 | 116 | monthSpentMicros: usage?.ok ? usage.value.spentMicros : null, | |
| 115 | 117 | }; | |
| 116 | 118 | } |
| 281 | 281 | action: "Add", | |
| 282 | 282 | }, | |
| 283 | 283 | { | |
| 284 | − | done: shell?.creditMicros == null || shell.creditMicros > 0, | |
| 284 | + | // Nothing to pay while g1t is being built out. | |
| 285 | + | done: Boolean(shell?.free) || shell?.creditMicros == null || shell.creditMicros > 0, | |
| 285 | 286 | title: "Add agent credit", | |
| 286 | 287 | about: "g1t's agents are paid for from the workspace's credit, at what the model costs plus 20%.", | |
| 287 | 288 | to: workspace ? `/${workspace}/-/billing` : null, |
| 78 | 78 | return ( | |
| 79 | 79 | <div className="grid gap-10 lg:grid-cols-[1fr_20rem]"> | |
| 80 | 80 | <div className="min-w-0"> | |
| 81 | + | {status.free && ( | |
| 82 | + | <div className="mb-8 rounded-xl border border-accent/30 bg-accent/5 p-5"> | |
| 83 | + | <h2 className="font-medium">Free while g1t is being built out</h2> | |
| 84 | + | <p className="mt-1.5 max-w-2xl text-sm text-muted"> | |
| 85 | + | While we build g1t out, using it costs nothing: agents, reviews, checks and workflows. Bring your own | |
| 86 | + | model provider under Integrations and its usage is billed by that provider, not by g1t. Runs are still | |
| 87 | + | recorded with what they cost, so Usage shows what you are using. This is for now, not forever: pricing | |
| 88 | + | will come later, and we will say so well before anything is charged. | |
| 89 | + | </p> | |
| 90 | + | </div> | |
| 91 | + | )} | |
| 81 | 92 | <h2 className="font-medium">Agent credit</h2> | |
| 82 | 93 | <p className="mt-1 max-w-2xl text-sm text-muted"> | |
| 83 | 94 | g1t agents that work on this workspace's repositories are paid for from |
| 215 | 215 | as it uses, with each | |
| 216 | 216 | kind of work routed to one of them or to g1t's hosted models | |
| 217 | 217 | (`PUT /workspaces/{workspace}/model-routes`). Those providers bill the | |
| 218 | − | workspace and g1t charges $0.10 a run. Sandboxes never hold a key. | |
| 218 | + | workspace; g1t charges nothing while it is being built out (later, $0.10 | |
| 219 | + | a run). Sandboxes never hold a key. | |
| 219 | 220 | - **Alerts** (`sentry`, `datadog`, `webhook`): each problem opens one issue | |
| 220 | 221 | in a chosen repository, optionally with an agent put on it at once. | |
| 221 | 222 | Senders sign requests to `https://api.g1t.sh/hooks/{integration}`. |
| 27 | 27 | /// False while the payment provider is in its test mode, where cards | |
| 28 | 28 | /// are not real. | |
| 29 | 29 | pub live: bool, | |
| 30 | + | /// True while g1t is being built out: runs are recorded, with what | |
| 31 | + | /// they cost, but nothing is charged and no credit is needed. Not a | |
| 32 | + | /// promise that it stays free. | |
| 33 | + | #[serde(default)] | |
| 34 | + | pub free: bool, | |
| 30 | 35 | } | |
| 31 | 36 | ||
| 32 | 37 | /// A workspace's standing. |
| 14 | 14 | enabled: boolean; | |
| 15 | 15 | /** False while the card processor is in its test mode, where cards are not real. */ | |
| 16 | 16 | live: boolean; | |
| 17 | + | /** | |
| 18 | + | * True while g1t is being built out: runs are recorded with what they | |
| 19 | + | * cost, but nothing is charged and no credit is needed. Not forever. | |
| 20 | + | */ | |
| 21 | + | free?: boolean; | |
| 17 | 22 | }; | |
| 18 | 23 | ||
| 19 | 24 | /** A workspace's standing. */ |
| 6 | 6 | use g1t_actions::workflow::{self, Trigger, Workflow}; | |
| 7 | 7 | use g1t_contracts::actions::{DispatchArgs, WorkflowRun}; | |
| 8 | 8 | use g1t_contracts::events::Event; | |
| 9 | − | use g1t_contracts::identity::{AGENT_ID, AGENT_NAME, UsernameArgs, UsernamesArgs}; | |
| 9 | + | use g1t_contracts::identity::{AGENT_ID, AGENT_NAME, UsernamesArgs}; | |
| 10 | 10 | use g1t_contracts::repos::{Commit, CompareArgs, Comparison, LogArgs, Repo, RepoPath}; | |
| 11 | 11 | use g1t_contracts::work::{IssueDetail, PullDetail, ViewArgs}; | |
| 12 | − | use g1t_contracts::{FailureCode, Outcome, User, Viewer, new_id}; | |
| 12 | + | use g1t_contracts::{FailureCode, Outcome, User, new_id}; | |
| 13 | 13 | use g1t_kit::now_ms; | |
| 14 | 14 | use serde_json::{Map, Value, json}; | |
| 15 | 15 | use worker::Result; | |
| 51 | 51 | Ok(names.get(id).cloned()) | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | − | /// Whether someone belongs to the workspace, so their pull requests' | |
| 55 | − | /// runs get the secrets. | |
| 56 | − | async fn insider(&self, author: &User, namespace: &str) -> Result<bool> { | |
| 57 | − | if author.id == AGENT_ID || author.is_member(&namespace.to_lowercase()) { | |
| 54 | + | /// Whether a pull request's author belongs to the workspace, so its | |
| 55 | + | /// runs get the secrets and a token. On a private repository only | |
| 56 | + | /// members can open one at all. | |
| 57 | + | async fn insider(&self, author: &User, repo: &Repo, ws: &User) -> Result<bool> { | |
| 58 | + | let slug = repo.namespace.to_lowercase(); | |
| 59 | + | if repo.is_private || author.id == AGENT_ID || author.is_member(&slug) { | |
| 58 | 60 | return Ok(true); | |
| 59 | 61 | } | |
| 60 | − | let found: Viewer = g1t_kit::call(&self.identity, "user_by_username", &UsernameArgs { username: author.username.clone() }).await?; | |
| 61 | − | Ok(found.is_some_and(|user| user.is_member(&namespace.to_lowercase()))) | |
| 62 | + | // Stored authors carry no memberships: ask the workspace. | |
| 63 | + | let members: Outcome<Vec<g1t_contracts::identity::Member>> = g1t_kit::call( | |
| 64 | + | &self.identity, | |
| 65 | + | "list_members", | |
| 66 | + | &g1t_contracts::identity::ListMembersArgs { slug, viewer: Some(ws.clone()) }, | |
| 67 | + | ) | |
| 68 | + | .await?; | |
| 69 | + | Ok(members.into_result().unwrap_or_default().iter().any(|m| m.username.eq_ignore_ascii_case(&author.username))) | |
| 62 | 70 | } | |
| 63 | 71 | ||
| 64 | 72 | async fn commits(&self, repo: &Repo, actor: &User, after: &str, before: Option<&str>) -> Result<Vec<Commit>> { | |
| 195 | 203 | "user": review.map(|r| payload::user(&r.author.username)), | |
| 196 | 204 | }); | |
| 197 | 205 | } | |
| 198 | − | let trusted = self.insider(&pull.author, &repo.namespace).await?; | |
| 206 | + | let trusted = self.insider(&pull.author, repo, ws).await?; | |
| 199 | 207 | let head_ref = payload::head_ref(pull); | |
| 200 | 208 | if event_name == "pull_request_target" { | |
| 201 | 209 | // In the base's context: its workflows, its head. |
| 124 | 124 | margin_percent: u32, | |
| 125 | 125 | /// Charged for a run on the workspace's own model provider. | |
| 126 | 126 | orchestration_fee_micros: i64, | |
| 127 | + | /// While g1t is being built out, nothing is charged (`FREE_WHILE_BUILDING`). | |
| 128 | + | free: bool, | |
| 127 | 129 | } | |
| 128 | 130 | ||
| 129 | 131 | impl Billing { | |
| 131 | 133 | Status { | |
| 132 | 134 | enabled: self.stripe.is_some(), | |
| 133 | 135 | live: self.stripe.as_ref().is_some_and(Stripe::live), | |
| 136 | + | free: self.free, | |
| 134 | 137 | } | |
| 135 | 138 | } | |
| 136 | 139 | ||
| 443 | 446 | ||
| 444 | 447 | /// A refusal if the workspace has no credit to start an agent with. | |
| 445 | 448 | async fn out_of_credit<T>(&self, workspace: &str) -> Result<Option<Outcome<T>>> { | |
| 449 | + | // While g1t is being built out, no one needs credit. | |
| 450 | + | if self.free { | |
| 451 | + | return Ok(None); | |
| 452 | + | } | |
| 446 | 453 | let balance = self | |
| 447 | 454 | .row(workspace) | |
| 448 | 455 | .await? | |
| 531 | 538 | } | |
| 532 | 539 | // On the workspace's own provider, the model was paid for there: | |
| 533 | 540 | // g1t charges its fee, and keeps the provider's cost to show. | |
| 534 | − | let charge = if run.own_provider() { | |
| 541 | + | let charge = if self.free { | |
| 542 | + | // Recorded, with what it cost, but not charged. | |
| 543 | + | 0 | |
| 544 | + | } else if run.own_provider() { | |
| 535 | 545 | self.orchestration_fee_micros | |
| 536 | 546 | } else { | |
| 537 | 547 | charge_micros(a.cost_usd, self.margin_percent) | |
| 545 | 555 | if run.own_provider() { | |
| 546 | 556 | description.push_str(", on your own model provider"); | |
| 547 | 557 | } | |
| 558 | + | if self.free { | |
| 559 | + | description.push_str(" (free while g1t is being built out)"); | |
| 560 | + | } | |
| 548 | 561 | self.enter( | |
| 549 | 562 | &run.workspace, | |
| 550 | 563 | EntryKind::Usage, | |
| 592 | 605 | .ok() | |
| 593 | 606 | .and_then(|fee| fee.to_string().parse().ok()) | |
| 594 | 607 | .unwrap_or(100_000), | |
| 608 | + | free: env.var("FREE_WHILE_BUILDING").is_ok_and(|v| v.to_string() == "true"), | |
| 595 | 609 | }; | |
| 596 | 610 | match method.as_str() { | |
| 597 | 611 | "status" => reply(&billing.status()), |
| 25 | 25 | // What a run on a workspace's own model provider is charged, in | |
| 26 | 26 | // millionths of a dollar: the sandbox and the orchestration around | |
| 27 | 27 | // it, with the model paid for at the provider. $0.10. | |
| 28 | − | "ORCHESTRATION_FEE_MICROS": "100000" | |
| 28 | + | "ORCHESTRATION_FEE_MICROS": "100000", | |
| 29 | + | // While g1t is being built out, workspaces pay nothing: runs are | |
| 30 | + | // recorded with what they cost, and nothing is charged. Set to | |
| 31 | + | // "false" when pricing starts. | |
| 32 | + | "FREE_WHILE_BUILDING": "true" | |
| 29 | 33 | }, | |
| 30 | 34 | // Secret: STRIPE_SECRET_KEY. Without it nothing is charged and the | |
| 31 | 35 | // runner decides who may start agents some other way. |
| 555 | 555 | repo: RepoPath; | |
| 556 | 556 | timeoutMinutes: number; | |
| 557 | 557 | }): Promise<Result<true>> { | |
| 558 | − | const status = await billingClient(this.env.BILLING).status(); | |
| 559 | − | if (!(this.previewListed(args.repo.namespace) || (status.enabled && status.live))) { | |
| 558 | + | // The same workspaces that may use g1t's sandboxes for agents. | |
| 559 | + | if (!(await this.workspaceAllowed(args.repo.namespace))) { | |
| 560 | 560 | return { | |
| 561 | 561 | ok: false, | |
| 562 | 562 | error: { | |
| 563 | 563 | code: "forbidden", | |
| 564 | − | message: "Workflows run on g1t's hosted runners, which are not open to this workspace yet.", | |
| 564 | + | message: | |
| 565 | + | "Workflows run on g1t's runners for workspaces that use g1t's agents: connect your own model provider under Integrations, free while g1t is being built out.", | |
| 565 | 566 | }, | |
| 566 | 567 | }; | |
| 567 | 568 | } |
| 627 | 627 | .map(|s| { | |
| 628 | 628 | let run = s.target_url.as_deref().and_then(|url| url.rsplit('/').next()).unwrap_or_default(); | |
| 629 | 629 | format!( | |
| 630 | − | "- {} ({}): run `{run}`", | |
| 630 | + | "- {} ({}): run `{run}`, {}", | |
| 631 | 631 | s.context, | |
| 632 | − | s.description.as_deref().unwrap_or("failed") | |
| 632 | + | s.description.as_deref().unwrap_or("failed"), | |
| 633 | + | s.target_url.as_deref().unwrap_or_default() | |
| 633 | 634 | ) | |
| 634 | 635 | }) | |
| 635 | 636 | .collect(); | |
| 637 | + | // Named outright: the agent cannot guess it from its fork. | |
| 638 | + | let repo = g1t_kit::call::<_, Option<RepoPath>>( | |
| 639 | + | &self.repos, | |
| 640 | + | "path_by_id", | |
| 641 | + | &g1t_contracts::repos::PathByIdArgs { id: pull.repo_id.clone() }, | |
| 642 | + | ) | |
| 643 | + | .await? | |
| 644 | + | .map(|path| format!("{}/{}", path.namespace, path.name)) | |
| 645 | + | .unwrap_or_default(); | |
| 636 | 646 | Ok(format!( | |
| 637 | − | "These GitHub Actions workflows failed on your latest commit:\n\n{}\n\n\ | |
| 638 | − | Read why with the `get_workflow_run` tool (this repository, and the run's id), \ | |
| 647 | + | "These GitHub Actions workflows failed on your latest commit to {repo}:\n\n{}\n\n\ | |
| 648 | + | Read why with the `get_workflow_run` tool (repo `{repo}` and the run's id), \ | |
| 639 | 649 | then `get_job_logs` for the job that failed. Fix the cause in the code, not the workflow, \ | |
| 640 | 650 | unless the workflow itself is wrong.", | |
| 641 | 651 | failed.join("\n") |