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

Chat and workspace agents: channels, DMs and named agents you talk to1/**
2 * One reply: an agent answering a message in chat, in this Worker, with no
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.3 * sandbox.
Chat and workspace agents: channels, DMs and named agents you talk to4 *
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked5 * A reply is quick and bounded: it reads the latest messages of the
6 * conversation (never all of it), what the agent remembers for this place,
7 * and its recent sessions here. When a request needs real work, the agent
8 * spins off a session (sessions.ts) and says so; the session reports back.
9 * A message in a session's card thread is not a reply at all: it steers
10 * that session.
Chat and workspace agents: channels, DMs and named agents you talk to11 *
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked12 * A reply is metered exactly as a session step is (`metered`, meter.ts):
13 * the agent's and the workspace's agent budgets, model routing, the
14 * compute gate, a billing run, the model proxy, and the spend recorded.
Chat and workspace agents: channels, DMs and named agents you talk to15 *
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked16 * Whatever happens, the reply's row says so: replied, steered, blocked
17 * (with one short notice in the conversation, not repeated), skipped or
18 * failed (with one short apology).
Chat and workspace agents: channels, DMs and named agents you talk to19 */
Merge branch 'worktree-agent-a1398e81ad1a64c5f'20import { type AgentDelivery, type ServiceBinding, identityClient, newId } from "@g1t/contracts";
Chat and workspace agents: channels, DMs and named agents you talk to21
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)22import { CHAT_MAX_HOPS } from "../../../packages/contracts/src/chat.ts";
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked23import type { Tokens } from "./budget.ts";
Merge branch 'worktree-agent-a1398e81ad1a64c5f'24import { handOffPort } from "./handoff.ts";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)25import { HISTORY_LIMIT, fixedHello, helloAsk, systemPrompt, turns } from "./prompt.ts";
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.26import { loadShelf, skillsSection, teamSlugs } from "./skills.ts";
27import { readVersion } from "./skill-library.ts";
Merge branch 'worktree-agent-a1398e81ad1a64c5f'28import { type Specialist, orchestratorInstructions, orchestratorTier, rosterLines } from "./orchestrator.ts";
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked29import { type MeterEnv, metered } from "./meter.ts";
30import { type RecallPlace, memorySection, recall } from "./memory.ts";
Agents recall what Docs say before they answer or work, and each has required reading31import { recallQuery, recallSection } from "./recall.ts";
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.32import { REPLY_TIER, allowedProviders, effortOf, effortPlan, replyModel } from "./routing.ts";
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked33import { type SessionEnv, type SessionRow, actionPorts, sessionRow, startSession, steer } from "./sessions.ts";
34import { type Row, definitionOf, periods, selectAgents, toAgent } from "./store.ts";
Chat and workspace agents: channels, DMs and named agents you talk to35import { type SurfaceMessage, surfaceFor } from "./surface.ts";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)36import { Audience } from "./audience.ts";
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.37import { audiencePorts, loadTeams, toolPorts } from "./ports.ts";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)38import { type ToolCall, type ToolPorts, ToolBox } from "./tools.ts";
39import type { Surface } from "./surface.ts";
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked40import type { Desk } from "./desk.ts";
41import { type ModelMessage, type Send, NO_TOKENS, addTokens, runTurn } from "./turn.ts";
42import type { AgentRouting as Policy } from "../../runner/src/model-env.ts";
43import { dollars } from "./money.ts";
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.44import { teamsSection } from "./teammates.ts";
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.45import { personalRefusal } from "./access.ts";
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked46
47export { billingRepo } from "./meter.ts";
Chat and workspace agents: channels, DMs and named agents you talk to48
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked49export type ReplyEnv = MeterEnv & {
Chat and workspace agents: channels, DMs and named agents you talk to50 DB: D1Database;
51 CHAT: ServiceBinding;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)52 /** People and their access, for the audience; members and teams for the roster. */
53 IDENTITY: ServiceBinding;
54 /** Code, for read tools. */
55 REPOS: ServiceBinding;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked56 /** Issues and pull requests, for read tools and filing issues. */
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)57 WORK: ServiceBinding;
58 /** Code search, for read tools. */
59 SEARCH: ServiceBinding;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked60 /** Notifications: a session waiting for more budget. */
61 NOTIFY?: ServiceBinding;
62 /** Every agent's desk: sessions are worked on their agent's. */
63 DESKS: DurableObjectNamespace<Desk>;
Chat and workspace agents: channels, DMs and named agents you talk to64};
65
66/** A notice that the agent cannot reply is posted once per conversation in this long. */
67const NOTICE_QUIET_MS = 6 * 60 * 60 * 1000;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked68/** Sessions one reply may start. */
69const MAX_SPIN_OFFS = 2;
Chat and workspace agents: channels, DMs and named agents you talk to70
71const APOLOGY = "Sorry, something went wrong on my side and I couldn't answer that. Try again in a moment.";
72
73/** Who asked, from the message that woke the agent (or their latest one). */
74function askerIn(history: SurfaceMessage[], delivery: AgentDelivery): SurfaceMessage["author"] | null {
75 const woken = history.find((m) => m.id === delivery.message_id);
76 if (woken) return woken.author;
77 return [...history].reverse().find((m) => m.author.kind === "user" && m.author.id === delivery.asked_by)?.author ?? null;
78}
79
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)80/**
81 * An agent's colleagues: every agent of the workspace but itself that is
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.82 * not archived (docs.g1t.sh/guides/agents/, "Agents know each other").
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)83 */
84async function team(db: D1Database, workspaceId: string, selfId: string, now: Date): Promise<Specialist[]> {
85 const rows = await db
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.86 // Personal agents are their members' own: no colleague of anyone.
87 .prepare(`${selectAgents("a.workspace_id = ?3 AND a.archived_at IS NULL AND a.id <> ?4 AND a.scope = 'workspace'")} ORDER BY a.builtin DESC, a.handle LIMIT 50`)
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)88 .bind(...periods(now), workspaceId, selfId)
89 .all<Row>();
90 return rows.results.map((row) => {
91 const agent = toAgent(row, now);
92 return {
93 handle: agent.handle,
94 display_name: agent.display_name,
95 role: agent.role,
96 title: agent.title,
97 team: agent.team,
98 department: agent.department,
99 responsibilities: agent.responsibilities,
100 status: agent.status,
101 spent_month_micros: agent.spent_month_micros,
102 monthly_micros: agent.budget.monthly_micros,
103 };
104 });
105}
106
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked107/** The agent's latest sessions in this conversation, one line each. */
108async function sessionsHere(db: D1Database, agentId: string, channelId: string): Promise<string | null> {
109 const rows = await db
110 .prepare(
111 "SELECT id, title, status, summary, created_at FROM agent_sessions WHERE agent_id = ? AND channel_id = ? AND parent_id IS NULL ORDER BY created_at DESC LIMIT 5",
112 )
113 .bind(agentId, channelId)
114 .all<{ id: string; title: string; status: string; summary: string | null; created_at: string }>();
115 if (!rows.results.length) return null;
116 return rows.results
117 .map((s) => `- "${s.title}" (${s.status}, ${s.created_at.slice(0, 10)})${s.summary ? `: ${s.summary.replace(/\s+/g, " ").slice(0, 300)}` : ""}`)
118 .join("\n");
119}
120
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)121/**
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.122 * Consulting a colleague:
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)123 * the colleague answers in a nested turn that posts nothing, with the same
124 * audience (so it can read no more than the conversation may), the same
125 * asker, one hop further, its own routing limits, on the same model
126 * session, billed to this reply. A compact card in the thread says who
127 * asked whom. Consults don't nest: the colleague can't consult in turn.
128 */
129function consulting(input: {
130 db: D1Database;
131 row: Row;
132 delivery: DeskWork;
133 send: Send;
134 policy: Policy;
135 ownModel: boolean;
136 sessionModel: string | null;
137 own: string | null;
138 ports: (consult: ToolPorts["consult"]) => ToolPorts;
139 hops: number;
140 now: Date;
141 surface: Surface;
142 asker: { name: string; display_name: string | null; access: NonNullable<AgentDelivery["asker"]> | null };
143 spent: { tokens: Tokens; cost: number };
144}): { ask: ToolPorts["consult"]; attach(box: ToolBox): void } {
145 let parent: ToolBox | null = null;
146 const noNesting: ToolPorts["consult"] = async () => ({ ok: false, message: "Consults don't nest: answer with what you have." });
147 const ask: ToolPorts["consult"] = async (handle, question) => {
148 const { row, delivery } = input;
149 const colleague = await input.db
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.150 .prepare("SELECT * FROM agents WHERE workspace_id = ? AND handle = ? AND archived_at IS NULL AND scope = 'workspace'")
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)151 .bind(row.workspace_id, handle)
152 .first<Row>();
153 if (!colleague || colleague.id === row.id) return { ok: false, message: `There is no other agent called @${handle} here.` };
154 // No ping-pong: never back to the agent that sent this work.
155 if ((delivery.chain ?? []).at(-1) === colleague.id) return { ok: false, message: `@${handle} sent you this work; answer with what you have.` };
156 if (input.hops + 1 > CHAT_MAX_HOPS) return { ok: false, message: "This request has been passed along too many times; answer with what you have." };
157 const definition = definitionOf(colleague);
158 const allowed = allowedProviders(definition.routing, input.own);
159 if (input.ownModel ? !allowed.own : !allowed.hosted) return { ok: false, message: `@${handle} can't use the model this conversation runs on.` };
160 const model = replyModel(
161 input.policy,
162 { ...definition.routing, pinned: input.ownModel ? definition.routing.pinned : null },
163 { named: input.ownModel ? input.sessionModel : null },
164 );
165 const tools = parent
166 ? parent.forColleague(input.ports(noNesting), { agentId: colleague.id, notConsult: [colleague.handle, row.handle], hops: input.hops + 1, maxHops: input.hops + 1 })
167 : null;
168 const system = systemPrompt({
169 agent: { ...definition, id: colleague.id },
170 workspace: delivery.workspace,
171 channel: { kind: delivery.channel_kind, name: delivery.channel_name },
172 asker: input.asker,
173 today: input.now,
174 tools: tools ? { code: tools.definitions().some((tool) => tool.name === "read_file") } : null,
175 consultedBy: row.handle,
176 });
177 const result = await runTurn(input.send, {
178 model: model.model,
179 system,
180 messages: [{ role: "user", content: `@${row.handle} (agent) asks you: ${question}` }],
181 tools,
182 price: input.ownModel ? null : model.price,
183 });
184 input.spent.tokens = addTokens(input.spent.tokens, result.tokens);
185 input.spent.cost += input.ownModel ? 0 : result.cost;
186 const answer = result.text || "(no answer)";
187 const exchange = `Q: ${question}\nA: ${answer}`;
188 // The exchange, collapsed, in the thread. No @ in its text: it wakes nobody.
189 await input.surface
190 .post(`${row.display_name} asked ${colleague.display_name}`, {
191 kind: "consult",
192 title: `${row.display_name} asked ${colleague.display_name}`,
193 detail: exchange.length > 1000 ? `${exchange.slice(0, 1000)}…` : exchange,
194 state: null,
195 href: null,
196 })
197 .catch((error: unknown) => console.error("agents: a consult card was not posted", String(error)));
198 return { ok: true, colleague: colleague.handle, answer };
199 };
200 return {
201 ask,
202 attach(box) {
203 parent = box;
204 },
205 };
206}
207
Chat and workspace agents: channels, DMs and named agents you talk to208type Outcome = {
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked209 status: "replied" | "steered" | "blocked" | "skipped" | "failed";
Chat and workspace agents: channels, DMs and named agents you talk to210 error?: string | null;
211 reply_id?: string | null;
212 model?: string | null;
213 tier?: string | null;
214 tokens?: Tokens;
215 cost?: number;
216 charged?: number;
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.217 /** The effort level it ran at. */
218 effort?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to219};
220
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)221/**
222 * What a desk is handed: a message to answer, or (`hello`) the agent's
223 * first message to the person who made it, in the DM that just opened.
224 */
225export type DeskWork = AgentDelivery & { hello?: boolean };
226
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked227/** How a limit's refusal reads in chat, in the agent's voice. */
228function noticeFor(reason: string, message: string): string {
229 if (reason.startsWith("budget_") || reason === "workspace_agent_budget" || message.startsWith("My settings") || message.startsWith("I ")) return message;
230 if (reason === "no_model") return `I can't reply yet: ${message}`;
231 return `I can't reply right now: ${message}`;
232}
233
234/** The session a message in this thread is for: a reply under one of this agent's session cards. */
235async function steeredSession(db: D1Database, agentId: string, threadRoot: string | null): Promise<SessionRow | null> {
236 if (!threadRoot) return null;
237 const found = await db.prepare("SELECT id FROM agent_sessions WHERE agent_id = ? AND card_message_id = ?").bind(agentId, threadRoot).first<{ id: string }>();
238 return found ? sessionRow(db, found.id) : null;
239}
240
241/**
242 * Answers `delivery` as its agent. Never throws: every way it ends is
243 * recorded on the reply's row. A message handed over twice is answered
244 * once.
245 */
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)246export async function reply(env: ReplyEnv, delivery: DeskWork, now = new Date()): Promise<void> {
Chat and workspace agents: channels, DMs and named agents you talk to247 const db = env.DB;
248 const row = await db.prepare("SELECT * FROM agents WHERE id = ?").bind(delivery.agent_id).first<Row>();
249 if (!row || row.archived_at || row.workspace_id !== delivery.workspace_id) return;
250 const id = newId("arp", now.getTime());
251 const claimed = await db
252 .prepare(
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked253 `INSERT INTO agent_replies (id, agent_id, workspace_id, channel_id, message_id, asked_by, asked_by_username, channel_name, agent_version, status, created_at)
254 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'working', ?)
Chat and workspace agents: channels, DMs and named agents you talk to255 ON CONFLICT (agent_id, message_id) DO NOTHING RETURNING id`,
256 )
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked257 .bind(id, row.id, row.workspace_id, delivery.channel_id, delivery.message_id, delivery.asked_by, delivery.asker?.username ?? null, delivery.channel_name, row.version, now.toISOString())
Chat and workspace agents: channels, DMs and named agents you talk to258 .first<{ id: string }>();
259 if (!claimed) return;
260
261 const surface = surfaceFor(env.CHAT, delivery);
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)262 // 👀 as soon as the desk has it, while everything else goes on.
263 const acknowledging = surface.acknowledge();
Chat and workspace agents: channels, DMs and named agents you talk to264 const slug = delivery.workspace.toLowerCase();
265
266 // What the answer used, once there is one: counted however the reply ends.
267 let usage: Partial<Outcome> = {};
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked268 // What the tools did, for the audit table, and who the audience was.
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)269 let toolCalls: ToolCall[] = [];
270 let audienceHash: string | null = null;
Chat and workspace agents: channels, DMs and named agents you talk to271
272 const finish = async (outcome: Outcome) => {
273 const statements = [
274 db
275 .prepare(
276 `UPDATE agent_replies SET status = ?, error = ?, reply_id = ?, model = ?, tier = ?, input_tokens = ?, output_tokens = ?,
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.277 cost_micros = ?, charged_micros = ?, tool_count = ?, finished_at = ?, effort = ? WHERE id = ?`,
Chat and workspace agents: channels, DMs and named agents you talk to278 )
279 .bind(
280 outcome.status,
281 outcome.error?.slice(0, 1000) ?? null,
282 outcome.reply_id ?? null,
283 outcome.model ?? null,
284 outcome.tier ?? null,
285 (outcome.tokens?.input ?? 0) + (outcome.tokens?.cacheRead ?? 0) + (outcome.tokens?.cacheWrite ?? 0),
286 outcome.tokens?.output ?? 0,
287 outcome.cost ?? 0,
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked288 outcome.charged ?? 0,
289 toolCalls.length,
Chat and workspace agents: channels, DMs and named agents you talk to290 new Date().toISOString(),
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.291 outcome.effort ?? null,
Chat and workspace agents: channels, DMs and named agents you talk to292 id,
293 ),
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)294 // Every tool call: what was asked, for whom, and whether it was read or withheld.
295 ...toolCalls.map((call, n) =>
296 db
297 .prepare(
298 `INSERT INTO agent_tool_calls (id, reply_id, agent_id, workspace_id, asked_by, tool, args, audience_hash, outcome, bytes, created_at)
299 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
300 )
301 .bind(`${id}_${n}`, id, row.id, row.workspace_id, delivery.asked_by, call.tool, call.args, audienceHash ?? "", call.outcome, call.bytes, now.toISOString()),
302 ),
Chat and workspace agents: channels, DMs and named agents you talk to303 ];
304 await db.batch(statements);
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)305 // ✅ when it answered; 👀 taken back after a notice, an apology or nothing.
306 await acknowledging;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked307 await surface.settle(outcome.status === "replied" || outcome.status === "steered" ? "done" : "withdrawn");
Chat and workspace agents: channels, DMs and named agents you talk to308 };
309
310 /** Says once, in this conversation, why the agent cannot answer; a repeat within hours is kept back. */
311 const notice = async (message: string, reason: string) => {
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)312 // An agent's first words are never an excuse: without a model, a fixed hello.
313 if (delivery.hello) {
314 const posted = await surface.post(fixedHello(row, delivery.asker?.username ?? null)).catch(() => null);
315 return await finish({ status: "blocked", error: reason, reply_id: posted });
316 }
Chat and workspace agents: channels, DMs and named agents you talk to317 const since = new Date(now.getTime() - NOTICE_QUIET_MS).toISOString();
318 const recent = await db
319 .prepare(
320 `SELECT 1 FROM agent_replies WHERE agent_id = ? AND channel_id = ? AND status = 'blocked' AND error = ?
321 AND reply_id IS NOT NULL AND created_at > ? AND id <> ? LIMIT 1`,
322 )
323 .bind(row.id, delivery.channel_id, reason, since, id)
324 .first();
325 const posted = recent ? null : await surface.post(message).catch(() => null);
326 await finish({ status: "blocked", error: reason, reply_id: posted });
327 };
328
329 try {
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked330 // A reply under one of its session cards steers that session; nothing is answered here.
331 const steered = delivery.hello ? null : await steeredSession(db, row.id, delivery.thread_root);
332 if (steered) {
333 const history = await surface.history(HISTORY_LIMIT).catch(() => [] as SurfaceMessage[]);
334 const message = history.find((m) => m.id === delivery.message_id);
335 if (message?.body.trim()) {
336 await steer(env as unknown as SessionEnv, steered, message.author.name, message.body);
337 return await finish({ status: "steered" });
338 }
Chat and workspace agents: channels, DMs and named agents you talk to339 }
340
341 // Read the conversation while showing that the agent is on it.
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)342 // A hello has no conversation yet: it is asked to introduce itself.
Merge branch 'worktree-agent-a1398e81ad1a64c5f'343 // And who is here: said every turn, so the agent knows who reads it and who doesn't.
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.344 // And its teams, from their pages: who leads, who owns what, who is around.
345 const [, history, conversationHere, teamsHere] = await Promise.all([
Merge branch 'worktree-agent-a1398e81ad1a64c5f'346 surface.typing(),
347 delivery.hello ? Promise.resolve([]) : surface.history(HISTORY_LIMIT),
348 delivery.hello ? Promise.resolve(null) : surface.conversation(),
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.349 loadTeams(env, slug, row.workspace_id, { id: row.id, team: row.team ?? null }).catch(() => null),
Merge branch 'worktree-agent-a1398e81ad1a64c5f'350 ]);
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.351 // A personal agent answers only its member, in the DM of the two of them (access.ts).
352 const refused = personalRefusal(row, {
353 asked_by: delivery.asked_by,
354 channel_kind: delivery.channel_kind,
355 members: conversationHere ? conversationHere.people + conversationHere.agents : null,
356 });
357 if (refused) return await notice(refused, "personal_agent");
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)358 const conversation = delivery.hello ? [{ role: "user" as const, content: helloAsk(delivery.asker?.username ?? null) }] : turns(history, row.id);
Chat and workspace agents: channels, DMs and named agents you talk to359 if (!conversation.length) return await finish({ status: "skipped", error: "nothing to answer" });
360 const author = askerIn(history, delivery);
361 const askerName = delivery.asker?.username ?? author?.name ?? null;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)362 // Every agent knows its colleagues; @g1t also steps up a tier to decide
363 // who gets the work in a long thread.
364 const specialists = await team(db, row.workspace_id, row.id, now);
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked365 const definition = definitionOf(row);
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.366 // Its effort setting decides where the reply starts and how hard the model reasons.
367 const plan = effortPlan(effortOf(definition.routing), row.builtin ? orchestratorTier(history.length, specialists.filter((a) => a.handle !== "g1t").length) : REPLY_TIER);
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)368 const hops = Math.max(0, Math.floor(delivery.hops || 0));
369 const chain = delivery.chain ?? [];
370 const sender = chain.length ? await db.prepare("SELECT handle FROM agents WHERE id = ?").bind(chain[chain.length - 1]).first<{ handle: string }>() : null;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked371 const asker = { name: askerName ?? "someone", display_name: author?.display_name ?? null, access: delivery.asker ?? null };
372 let posted: string | null = null;
373 let spinOffs = 0;
374
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.375 const done = await metered(env, { row, payer: row, slug, task: "reply", start: plan.start, effort: plan.level, askerName, person: delivery.asker?.username ?? null, teams: teamsHere }, async (model) => {
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked376 // What colleagues consulted along the way used: billed to this reply.
377 const consulted = { tokens: NO_TOKENS, cost: 0 };
378 let toolbox: ToolBox | null = null;
379 let place: RecallPlace = { channel_id: delivery.channel_id, kind: delivery.channel_kind === "dm" ? "dm" : "private", people: [delivery.asked_by] };
380 if (!delivery.hello) {
381 try {
382 const audience = await Audience.build(slug, delivery.asked_by, audiencePorts(env, slug, delivery.channel_id));
383 place = { channel_id: delivery.channel_id, kind: audience.kind, people: audience.shared ? [delivery.asked_by] : audience.members.map((m) => m.id) };
Docs: a workspace knowledge base people and agents write together384 const ports = (consult: ToolPorts["consult"]) => toolPorts(env, slug, row.workspace_id, delivery.channel_id, consult, row.id);
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked385 audienceHash = audience.hash;
386 const consult = consulting({
387 db,
388 row,
389 delivery,
390 send: model.send,
391 policy: model.policy,
392 ownModel: model.ownModel,
393 sessionModel: model.sessionModel,
394 own: model.own,
395 ports,
396 hops,
397 now,
398 surface,
399 asker,
400 spent: consulted,
401 });
402 const where = delivery.channel_kind === "dm" ? "a direct message" : `#${delivery.channel_name ?? "a channel"}`;
403 const actions = actionPorts(env as unknown as SessionEnv, {
404 agent: row,
405 place,
406 source: { kind: "message", ref: delivery.message_id, label: askerName ? `@${askerName} in ${where}` : where, channel_id: delivery.channel_id },
407 asker: { id: delivery.asked_by, username: askerName },
408 workspace: slug,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar409 postCard: (card) => surface.post("", card).catch(() => null),
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked410 // Real work becomes a session, with its card in this conversation.
411 spinOff: async (title, goal) => {
412 if (spinOffs >= MAX_SPIN_OFFS) return { ok: false, message: "You've started enough sessions from this message." };
413 spinOffs++;
414 const session = await startSession(env as unknown as SessionEnv, {
415 agent: row,
416 kind: "chat",
417 title,
418 goal: `${askerName ? `@${askerName}` : "Someone"} asked in ${where}:\n\n${goal}`,
419 workspace: slug,
420 channel_id: delivery.channel_id,
421 channel_kind: delivery.channel_kind,
422 channel_name: delivery.channel_name,
423 thread_root: delivery.thread_root,
424 message_id: delivery.message_id,
425 asked_by: delivery.asked_by,
426 asked_by_username: askerName,
427 asker: delivery.asker ?? null,
428 chain: [...chain],
429 hops,
430 });
431 const cap = session.cap_micros ? ` with a cap of ${dollars(session.cap_micros)}` : "";
432 return { ok: true, message: `Started the session "${session.title}"${cap}. Its card is in the conversation and it reports back there. Tell them in a sentence; don't do the work here.` };
433 },
434 });
Merge branch 'worktree-agent-a1398e81ad1a64c5f'435 // Work handed to a colleague: the only way an agent gets another working (handoff.ts).
436 const viewer = audience.asker;
437 actions.handOff = handOffPort({
438 self: { id: row.id, handle: row.handle },
439 chain,
440 asker: delivery.asker ?? null,
441 agent: async (handle) => {
442 const found = await db
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.443 .prepare(selectAgents("a.workspace_id = ?3 AND a.handle = ?4 AND a.archived_at IS NULL AND a.scope = 'workspace'"))
Merge branch 'worktree-agent-a1398e81ad1a64c5f'444 .bind(...periods(now), row.workspace_id, handle)
445 .first<Row>();
446 if (!found) return null;
447 const agent = toAgent(found, now);
448 return { id: agent.id, handle: agent.handle, display_name: agent.display_name, builtin: !!found.builtin, status: agent.status };
449 },
450 person: async (handle) => {
451 if (!viewer) return false;
452 const members = await identityClient(env.IDENTITY).listMembers(slug, viewer);
453 return members.ok && members.value.some((m) => m.username.toLowerCase() === handle);
454 },
455 handOff: (colleagueId, brief) => surface.handOff(colleagueId, brief),
456 });
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked457 toolbox = new ToolBox(
458 audience,
459 ports(consult.ask),
460 { agentId: row.id, notConsult: [row.handle, ...(sender ? [sender.handle] : [])], hops, maxHops: CHAT_MAX_HOPS },
461 [],
462 actions,
463 );
464 consult.attach(toolbox);
465 } catch (error) {
466 // Without an audience nothing may be read: the reply goes on with this conversation only.
467 console.error("agents: no audience for a reply, so no tools", row.id, String(error));
468 }
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)469 }
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.470 // What people said last, for recalling what the workspace's artifacts say about it.
Agents recall what Docs say before they answer or work, and each has required reading471 const said = [...history].reverse().filter((m) => m.author.kind === "user").slice(0, 3).map((m) => m.body);
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.472 const [facts, recent, passages, shelf] = delivery.hello
473 ? [[], null, [], []]
Agents recall what Docs say before they answer or work, and each has required reading474 : await Promise.all([
475 recall(db, row.id, place).catch(() => []),
476 sessionsHere(db, row.id, delivery.channel_id).catch(() => null),
477 toolbox ? toolbox.recall(recallQuery(said), definition.reading ?? []) : Promise.resolve([]),
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.478 // Its skills: named in the prompt, read with use_skill (skills.ts).
479 toolbox ? loadShelf(db, row.workspace_id, { id: row.id, skills_off: definition.skills_off }, teamSlugs(teamsHere, row.team ?? null)) : Promise.resolve([]),
Agents recall what Docs say before they answer or work, and each has required reading480 ]);
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.481 toolbox?.useShelf(shelf, (skillId, version) => readVersion(db, skillId, version));
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked482 const system = [
483 systemPrompt({
484 agent: {
485 ...definition,
486 id: row.id,
487 // @g1t's job is fixed; what the workspace wrote is added to it.
488 instructions: row.builtin ? orchestratorInstructions(specialists.filter((a) => a.handle !== "g1t"), definition.instructions) : definition.instructions,
489 },
490 workspace: delivery.workspace,
491 channel: { kind: delivery.channel_kind, name: delivery.channel_name },
492 asker,
493 today: now,
494 tools: toolbox ? { code: toolbox.definitions().some((tool) => tool.name === "read_file") } : null,
495 // @g1t's team is in its job; everyone else is told who their colleagues are.
496 colleagues: row.builtin ? null : rosterLines(specialists),
497 recentSessions: recent,
Merge branch 'worktree-agent-a1398e81ad1a64c5f'498 conversation: conversationHere,
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.499 teams: teamsSection(row.id, teamsHere, now),
Merge branch 'worktree-agent-a1398e81ad1a64c5f'500 canHandOff: !!toolbox?.definitions().some((tool) => tool.name === "hand_off"),
501 handedOffBy: sender?.handle ?? null,
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.502 skills: skillsSection(shelf, toolbox?.definitions().map((tool) => tool.name) ?? []),
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked503 }),
504 memorySection(facts),
Agents recall what Docs say before they answer or work, and each has required reading505 recallSection(passages),
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked506 ]
507 .filter(Boolean)
508 .join("\n\n");
509 toolCalls = toolbox?.calls ?? [];
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.510 const answer = await runTurn(model.send, { model: model.model.model, system, messages: conversation as ModelMessage[], tools: toolbox, price: model.ownModel ? null : model.model.price, effort: model.effort });
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked511 // Post as soon as there is an answer; the bill is settled after.
Merge branch 'worktree-agent-a1398e81ad1a64c5f'512 // It wakes nobody, @mentions or not: colleagues get work only through hand_off.
513 if (answer.text) posted = await surface.post(answer.text);
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked514 return {
515 text: answer.text,
516 tokens: addTokens(answer.tokens, consulted.tokens),
517 cost: (model.ownModel ? 0 : answer.cost) + consulted.cost,
518 rounds: answer.rounds,
519 };
Chat and workspace agents: channels, DMs and named agents you talk to520 });
521
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked522 if (!done.ok) return await notice(noticeFor(done.reason, done.message), done.reason);
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.523 usage = { model: done.model, tier: done.tier, tokens: done.tokens, cost: done.cost, charged: done.charged, effort: plan.level };
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked524 if (!done.value.text) {
525 const apology = await surface.post(APOLOGY).catch(() => null);
526 return await finish({ status: "failed", error: "the model gave no text", reply_id: apology, ...usage });
Chat and workspace agents: channels, DMs and named agents you talk to527 }
528 await finish({ status: "replied", reply_id: posted, ...usage });
529 } catch (error) {
530 const message = error instanceof Error ? error.message : String(error);
531 console.error("agents: a reply failed", row.id, delivery.message_id, message);
532 const posted = await surface.post(APOLOGY).catch(() => null);
533 await finish({ status: "failed", error: message, reply_id: posted, ...usage }).catch((failure: unknown) =>
534 console.error("agents: a failed reply was not recorded", id, String(failure)),
535 );
536 }
537}

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