Skip to content
533 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";
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.26import { skillsSection } from "./skills.ts";
Merge branch 'worktree-agent-a1398e81ad1a64c5f'27import { 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 asked28import { type MeterEnv, metered } from "./meter.ts";
29import { type RecallPlace, memorySection, recall } from "./memory.ts";
Agents recall what Docs say before they answer or work, and each has required reading30import { 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.31import { 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 asked32import { type SessionEnv, type SessionRow, actionPorts, sessionRow, startSession, steer } from "./sessions.ts";
33import { type Row, definitionOf, periods, selectAgents, toAgent } from "./store.ts";
Chat and workspace agents: channels, DMs and named agents you talk to34import { 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)35import { 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.36import { 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)37import { type ToolCall, type ToolPorts, ToolBox } from "./tools.ts";
38import 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 asked39import type { Desk } from "./desk.ts";
40import { type ModelMessage, type Send, NO_TOKENS, addTokens, runTurn } from "./turn.ts";
41import type { AgentRouting as Policy } from "../../runner/src/model-env.ts";
42import { 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.43import { 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.44import { 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 asked45
46export { billingRepo } from "./meter.ts";
Chat and workspace agents: channels, DMs and named agents you talk to47
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 asked48export type ReplyEnv = MeterEnv & {
Chat and workspace agents: channels, DMs and named agents you talk to49 DB: D1Database;
50 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)51 /** People and their access, for the audience; members and teams for the roster. */
52 IDENTITY: ServiceBinding;
53 /** Code, for read tools. */
54 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 asked55 /** 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)56 WORK: ServiceBinding;
57 /** Code search, for read tools. */
58 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 asked59 /** Notifications: a session waiting for more budget. */
60 NOTIFY?: ServiceBinding;
61 /** Every agent's desk: sessions are worked on their agent's. */
62 DESKS: DurableObjectNamespace<Desk>;
Chat and workspace agents: channels, DMs and named agents you talk to63};
64
65/** A notice that the agent cannot reply is posted once per conversation in this long. */
66const 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 asked67/** Sessions one reply may start. */
68const MAX_SPIN_OFFS = 2;
Chat and workspace agents: channels, DMs and named agents you talk to69
70const APOLOGY = "Sorry, something went wrong on my side and I couldn't answer that. Try again in a moment.";
71
72/** Who asked, from the message that woke the agent (or their latest one). */
73function askerIn(history: SurfaceMessage[], delivery: AgentDelivery): SurfaceMessage["author"] | null {
74 const woken = history.find((m) => m.id === delivery.message_id);
75 if (woken) return woken.author;
76 return [...history].reverse().find((m) => m.author.kind === "user" && m.author.id === delivery.asked_by)?.author ?? null;
77}
78
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)79/**
80 * 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.81 * 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)82 */
83async function team(db: D1Database, workspaceId: string, selfId: string, now: Date): Promise<Specialist[]> {
84 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.85 // Personal agents are their members' own: no colleague of anyone.
86 .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)87 .bind(...periods(now), workspaceId, selfId)
88 .all<Row>();
89 return rows.results.map((row) => {
90 const agent = toAgent(row, now);
91 return {
92 handle: agent.handle,
93 display_name: agent.display_name,
94 role: agent.role,
95 title: agent.title,
96 team: agent.team,
97 department: agent.department,
98 responsibilities: agent.responsibilities,
99 status: agent.status,
100 spent_month_micros: agent.spent_month_micros,
101 monthly_micros: agent.budget.monthly_micros,
102 };
103 });
104}
105
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 asked106/** The agent's latest sessions in this conversation, one line each. */
107async function sessionsHere(db: D1Database, agentId: string, channelId: string): Promise<string | null> {
108 const rows = await db
109 .prepare(
110 "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",
111 )
112 .bind(agentId, channelId)
113 .all<{ id: string; title: string; status: string; summary: string | null; created_at: string }>();
114 if (!rows.results.length) return null;
115 return rows.results
116 .map((s) => `- "${s.title}" (${s.status}, ${s.created_at.slice(0, 10)})${s.summary ? `: ${s.summary.replace(/\s+/g, " ").slice(0, 300)}` : ""}`)
117 .join("\n");
118}
119
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)120/**
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.121 * 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)122 * the colleague answers in a nested turn that posts nothing, with the same
123 * audience (so it can read no more than the conversation may), the same
124 * asker, one hop further, its own routing limits, on the same model
125 * session, billed to this reply. A compact card in the thread says who
126 * asked whom. Consults don't nest: the colleague can't consult in turn.
127 */
128function consulting(input: {
129 db: D1Database;
130 row: Row;
131 delivery: DeskWork;
132 send: Send;
133 policy: Policy;
134 ownModel: boolean;
135 sessionModel: string | null;
136 own: string | null;
137 ports: (consult: ToolPorts["consult"]) => ToolPorts;
138 hops: number;
139 now: Date;
140 surface: Surface;
141 asker: { name: string; display_name: string | null; access: NonNullable<AgentDelivery["asker"]> | null };
142 spent: { tokens: Tokens; cost: number };
143}): { ask: ToolPorts["consult"]; attach(box: ToolBox): void } {
144 let parent: ToolBox | null = null;
145 const noNesting: ToolPorts["consult"] = async () => ({ ok: false, message: "Consults don't nest: answer with what you have." });
146 const ask: ToolPorts["consult"] = async (handle, question) => {
147 const { row, delivery } = input;
148 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.149 .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)150 .bind(row.workspace_id, handle)
151 .first<Row>();
152 if (!colleague || colleague.id === row.id) return { ok: false, message: `There is no other agent called @${handle} here.` };
153 // No ping-pong: never back to the agent that sent this work.
154 if ((delivery.chain ?? []).at(-1) === colleague.id) return { ok: false, message: `@${handle} sent you this work; answer with what you have.` };
155 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." };
156 const definition = definitionOf(colleague);
157 const allowed = allowedProviders(definition.routing, input.own);
158 if (input.ownModel ? !allowed.own : !allowed.hosted) return { ok: false, message: `@${handle} can't use the model this conversation runs on.` };
159 const model = replyModel(
160 input.policy,
161 { ...definition.routing, pinned: input.ownModel ? definition.routing.pinned : null },
162 { named: input.ownModel ? input.sessionModel : null },
163 );
164 const tools = parent
165 ? parent.forColleague(input.ports(noNesting), { agentId: colleague.id, notConsult: [colleague.handle, row.handle], hops: input.hops + 1, maxHops: input.hops + 1 })
166 : null;
167 const system = systemPrompt({
168 agent: { ...definition, id: colleague.id },
169 workspace: delivery.workspace,
170 channel: { kind: delivery.channel_kind, name: delivery.channel_name },
171 asker: input.asker,
172 today: input.now,
173 tools: tools ? { code: tools.definitions().some((tool) => tool.name === "read_file") } : null,
174 consultedBy: row.handle,
175 });
176 const result = await runTurn(input.send, {
177 model: model.model,
178 system,
179 messages: [{ role: "user", content: `@${row.handle} (agent) asks you: ${question}` }],
180 tools,
181 price: input.ownModel ? null : model.price,
182 });
183 input.spent.tokens = addTokens(input.spent.tokens, result.tokens);
184 input.spent.cost += input.ownModel ? 0 : result.cost;
185 const answer = result.text || "(no answer)";
186 const exchange = `Q: ${question}\nA: ${answer}`;
187 // The exchange, collapsed, in the thread. No @ in its text: it wakes nobody.
188 await input.surface
189 .post(`${row.display_name} asked ${colleague.display_name}`, {
190 kind: "consult",
191 title: `${row.display_name} asked ${colleague.display_name}`,
192 detail: exchange.length > 1000 ? `${exchange.slice(0, 1000)}…` : exchange,
193 state: null,
194 href: null,
195 })
196 .catch((error: unknown) => console.error("agents: a consult card was not posted", String(error)));
197 return { ok: true, colleague: colleague.handle, answer };
198 };
199 return {
200 ask,
201 attach(box) {
202 parent = box;
203 },
204 };
205}
206
Chat and workspace agents: channels, DMs and named agents you talk to207type 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 asked208 status: "replied" | "steered" | "blocked" | "skipped" | "failed";
Chat and workspace agents: channels, DMs and named agents you talk to209 error?: string | null;
210 reply_id?: string | null;
211 model?: string | null;
212 tier?: string | null;
213 tokens?: Tokens;
214 cost?: number;
215 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.216 /** The effort level it ran at. */
217 effort?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to218};
219
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)220/**
221 * What a desk is handed: a message to answer, or (`hello`) the agent's
222 * first message to the person who made it, in the DM that just opened.
223 */
224export type DeskWork = AgentDelivery & { hello?: boolean };
225
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 asked226/** How a limit's refusal reads in chat, in the agent's voice. */
227function noticeFor(reason: string, message: string): string {
228 if (reason.startsWith("budget_") || reason === "workspace_agent_budget" || message.startsWith("My settings") || message.startsWith("I ")) return message;
229 if (reason === "no_model") return `I can't reply yet: ${message}`;
230 return `I can't reply right now: ${message}`;
231}
232
233/** The session a message in this thread is for: a reply under one of this agent's session cards. */
234async function steeredSession(db: D1Database, agentId: string, threadRoot: string | null): Promise<SessionRow | null> {
235 if (!threadRoot) return null;
236 const found = await db.prepare("SELECT id FROM agent_sessions WHERE agent_id = ? AND card_message_id = ?").bind(agentId, threadRoot).first<{ id: string }>();
237 return found ? sessionRow(db, found.id) : null;
238}
239
240/**
241 * Answers `delivery` as its agent. Never throws: every way it ends is
242 * recorded on the reply's row. A message handed over twice is answered
243 * once.
244 */
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)245export async function reply(env: ReplyEnv, delivery: DeskWork, now = new Date()): Promise<void> {
Chat and workspace agents: channels, DMs and named agents you talk to246 const db = env.DB;
247 const row = await db.prepare("SELECT * FROM agents WHERE id = ?").bind(delivery.agent_id).first<Row>();
248 if (!row || row.archived_at || row.workspace_id !== delivery.workspace_id) return;
249 const id = newId("arp", now.getTime());
250 const claimed = await db
251 .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 asked252 `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)
253 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'working', ?)
Chat and workspace agents: channels, DMs and named agents you talk to254 ON CONFLICT (agent_id, message_id) DO NOTHING RETURNING id`,
255 )
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 asked256 .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 to257 .first<{ id: string }>();
258 if (!claimed) return;
259
260 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)261 // 👀 as soon as the desk has it, while everything else goes on.
262 const acknowledging = surface.acknowledge();
Chat and workspace agents: channels, DMs and named agents you talk to263 const slug = delivery.workspace.toLowerCase();
264
265 // What the answer used, once there is one: counted however the reply ends.
266 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 asked267 // 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)268 let toolCalls: ToolCall[] = [];
269 let audienceHash: string | null = null;
Chat and workspace agents: channels, DMs and named agents you talk to270
271 const finish = async (outcome: Outcome) => {
272 const statements = [
273 db
274 .prepare(
275 `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.276 cost_micros = ?, charged_micros = ?, tool_count = ?, finished_at = ?, effort = ? WHERE id = ?`,
Chat and workspace agents: channels, DMs and named agents you talk to277 )
278 .bind(
279 outcome.status,
280 outcome.error?.slice(0, 1000) ?? null,
281 outcome.reply_id ?? null,
282 outcome.model ?? null,
283 outcome.tier ?? null,
284 (outcome.tokens?.input ?? 0) + (outcome.tokens?.cacheRead ?? 0) + (outcome.tokens?.cacheWrite ?? 0),
285 outcome.tokens?.output ?? 0,
286 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 asked287 outcome.charged ?? 0,
288 toolCalls.length,
Chat and workspace agents: channels, DMs and named agents you talk to289 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.290 outcome.effort ?? null,
Chat and workspace agents: channels, DMs and named agents you talk to291 id,
292 ),
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)293 // Every tool call: what was asked, for whom, and whether it was read or withheld.
294 ...toolCalls.map((call, n) =>
295 db
296 .prepare(
297 `INSERT INTO agent_tool_calls (id, reply_id, agent_id, workspace_id, asked_by, tool, args, audience_hash, outcome, bytes, created_at)
298 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
299 )
300 .bind(`${id}_${n}`, id, row.id, row.workspace_id, delivery.asked_by, call.tool, call.args, audienceHash ?? "", call.outcome, call.bytes, now.toISOString()),
301 ),
Chat and workspace agents: channels, DMs and named agents you talk to302 ];
303 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)304 // ✅ when it answered; 👀 taken back after a notice, an apology or nothing.
305 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 asked306 await surface.settle(outcome.status === "replied" || outcome.status === "steered" ? "done" : "withdrawn");
Chat and workspace agents: channels, DMs and named agents you talk to307 };
308
309 /** Says once, in this conversation, why the agent cannot answer; a repeat within hours is kept back. */
310 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)311 // An agent's first words are never an excuse: without a model, a fixed hello.
312 if (delivery.hello) {
313 const posted = await surface.post(fixedHello(row, delivery.asker?.username ?? null)).catch(() => null);
314 return await finish({ status: "blocked", error: reason, reply_id: posted });
315 }
Chat and workspace agents: channels, DMs and named agents you talk to316 const since = new Date(now.getTime() - NOTICE_QUIET_MS).toISOString();
317 const recent = await db
318 .prepare(
319 `SELECT 1 FROM agent_replies WHERE agent_id = ? AND channel_id = ? AND status = 'blocked' AND error = ?
320 AND reply_id IS NOT NULL AND created_at > ? AND id <> ? LIMIT 1`,
321 )
322 .bind(row.id, delivery.channel_id, reason, since, id)
323 .first();
324 const posted = recent ? null : await surface.post(message).catch(() => null);
325 await finish({ status: "blocked", error: reason, reply_id: posted });
326 };
327
328 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 asked329 // A reply under one of its session cards steers that session; nothing is answered here.
330 const steered = delivery.hello ? null : await steeredSession(db, row.id, delivery.thread_root);
331 if (steered) {
332 const history = await surface.history(HISTORY_LIMIT).catch(() => [] as SurfaceMessage[]);
333 const message = history.find((m) => m.id === delivery.message_id);
334 if (message?.body.trim()) {
335 await steer(env as unknown as SessionEnv, steered, message.author.name, message.body);
336 return await finish({ status: "steered" });
337 }
Chat and workspace agents: channels, DMs and named agents you talk to338 }
339
340 // 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)341 // A hello has no conversation yet: it is asked to introduce itself.
Merge branch 'worktree-agent-a1398e81ad1a64c5f'342 // 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.343 // And its teams, from their pages: who leads, who owns what, who is around.
344 const [, history, conversationHere, teamsHere] = await Promise.all([
Merge branch 'worktree-agent-a1398e81ad1a64c5f'345 surface.typing(),
346 delivery.hello ? Promise.resolve([]) : surface.history(HISTORY_LIMIT),
347 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.348 loadTeams(env, slug, row.workspace_id, { id: row.id, team: row.team ?? null }).catch(() => null),
Merge branch 'worktree-agent-a1398e81ad1a64c5f'349 ]);
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.350 // A personal agent answers only its member, in the DM of the two of them (access.ts).
351 const refused = personalRefusal(row, {
352 asked_by: delivery.asked_by,
353 channel_kind: delivery.channel_kind,
354 members: conversationHere ? conversationHere.people + conversationHere.agents : null,
355 });
356 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)357 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 to358 if (!conversation.length) return await finish({ status: "skipped", error: "nothing to answer" });
359 const author = askerIn(history, delivery);
360 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)361 // Every agent knows its colleagues; @g1t also steps up a tier to decide
362 // who gets the work in a long thread.
363 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 asked364 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.365 // Its effort setting decides where the reply starts and how hard the model reasons.
366 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)367 const hops = Math.max(0, Math.floor(delivery.hops || 0));
368 const chain = delivery.chain ?? [];
369 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 asked370 const asker = { name: askerName ?? "someone", display_name: author?.display_name ?? null, access: delivery.asker ?? null };
371 let posted: string | null = null;
372 let spinOffs = 0;
373
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.374 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 asked375 // What colleagues consulted along the way used: billed to this reply.
376 const consulted = { tokens: NO_TOKENS, cost: 0 };
377 let toolbox: ToolBox | null = null;
378 let place: RecallPlace = { channel_id: delivery.channel_id, kind: delivery.channel_kind === "dm" ? "dm" : "private", people: [delivery.asked_by] };
379 if (!delivery.hello) {
380 try {
381 const audience = await Audience.build(slug, delivery.asked_by, audiencePorts(env, slug, delivery.channel_id));
382 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 together383 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 asked384 audienceHash = audience.hash;
385 const consult = consulting({
386 db,
387 row,
388 delivery,
389 send: model.send,
390 policy: model.policy,
391 ownModel: model.ownModel,
392 sessionModel: model.sessionModel,
393 own: model.own,
394 ports,
395 hops,
396 now,
397 surface,
398 asker,
399 spent: consulted,
400 });
401 const where = delivery.channel_kind === "dm" ? "a direct message" : `#${delivery.channel_name ?? "a channel"}`;
402 const actions = actionPorts(env as unknown as SessionEnv, {
403 agent: row,
404 place,
405 source: { kind: "message", ref: delivery.message_id, label: askerName ? `@${askerName} in ${where}` : where, channel_id: delivery.channel_id },
406 asker: { id: delivery.asked_by, username: askerName },
407 workspace: slug,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar408 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 asked409 // Real work becomes a session, with its card in this conversation.
410 spinOff: async (title, goal) => {
411 if (spinOffs >= MAX_SPIN_OFFS) return { ok: false, message: "You've started enough sessions from this message." };
412 spinOffs++;
413 const session = await startSession(env as unknown as SessionEnv, {
414 agent: row,
415 kind: "chat",
416 title,
417 goal: `${askerName ? `@${askerName}` : "Someone"} asked in ${where}:\n\n${goal}`,
418 workspace: slug,
419 channel_id: delivery.channel_id,
420 channel_kind: delivery.channel_kind,
421 channel_name: delivery.channel_name,
422 thread_root: delivery.thread_root,
423 message_id: delivery.message_id,
424 asked_by: delivery.asked_by,
425 asked_by_username: askerName,
426 asker: delivery.asker ?? null,
427 chain: [...chain],
428 hops,
429 });
430 const cap = session.cap_micros ? ` with a cap of ${dollars(session.cap_micros)}` : "";
431 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.` };
432 },
433 });
Merge branch 'worktree-agent-a1398e81ad1a64c5f'434 // Work handed to a colleague: the only way an agent gets another working (handoff.ts).
435 const viewer = audience.asker;
436 actions.handOff = handOffPort({
437 self: { id: row.id, handle: row.handle },
438 chain,
439 asker: delivery.asker ?? null,
440 agent: async (handle) => {
441 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.442 .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'443 .bind(...periods(now), row.workspace_id, handle)
444 .first<Row>();
445 if (!found) return null;
446 const agent = toAgent(found, now);
447 return { id: agent.id, handle: agent.handle, display_name: agent.display_name, builtin: !!found.builtin, status: agent.status };
448 },
449 person: async (handle) => {
450 if (!viewer) return false;
451 const members = await identityClient(env.IDENTITY).listMembers(slug, viewer);
452 return members.ok && members.value.some((m) => m.username.toLowerCase() === handle);
453 },
454 handOff: (colleagueId, brief) => surface.handOff(colleagueId, brief),
455 });
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 asked456 toolbox = new ToolBox(
457 audience,
458 ports(consult.ask),
459 { agentId: row.id, notConsult: [row.handle, ...(sender ? [sender.handle] : [])], hops, maxHops: CHAT_MAX_HOPS },
460 [],
461 actions,
462 );
463 consult.attach(toolbox);
464 } catch (error) {
465 // Without an audience nothing may be read: the reply goes on with this conversation only.
466 console.error("agents: no audience for a reply, so no tools", row.id, String(error));
467 }
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)468 }
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.469 // 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 reading470 const said = [...history].reverse().filter((m) => m.author.kind === "user").slice(0, 3).map((m) => m.body);
471 const [facts, recent, passages] = delivery.hello
472 ? [[], null, []]
473 : await Promise.all([
474 recall(db, row.id, place).catch(() => []),
475 sessionsHere(db, row.id, delivery.channel_id).catch(() => null),
476 toolbox ? toolbox.recall(recallQuery(said), definition.reading ?? []) : Promise.resolve([]),
477 ]);
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 asked478 const system = [
479 systemPrompt({
480 agent: {
481 ...definition,
482 id: row.id,
483 // @g1t's job is fixed; what the workspace wrote is added to it.
484 instructions: row.builtin ? orchestratorInstructions(specialists.filter((a) => a.handle !== "g1t"), definition.instructions) : definition.instructions,
485 },
486 workspace: delivery.workspace,
487 channel: { kind: delivery.channel_kind, name: delivery.channel_name },
488 asker,
489 today: now,
490 tools: toolbox ? { code: toolbox.definitions().some((tool) => tool.name === "read_file") } : null,
491 // @g1t's team is in its job; everyone else is told who their colleagues are.
492 colleagues: row.builtin ? null : rosterLines(specialists),
493 recentSessions: recent,
Merge branch 'worktree-agent-a1398e81ad1a64c5f'494 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.495 teams: teamsSection(row.id, teamsHere, now),
Merge branch 'worktree-agent-a1398e81ad1a64c5f'496 canHandOff: !!toolbox?.definitions().some((tool) => tool.name === "hand_off"),
497 handedOffBy: sender?.handle ?? null,
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.498 skills: skillsSection(definition.skills_off, 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 asked499 }),
500 memorySection(facts),
Agents recall what Docs say before they answer or work, and each has required reading501 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 asked502 ]
503 .filter(Boolean)
504 .join("\n\n");
505 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.506 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 asked507 // Post as soon as there is an answer; the bill is settled after.
Merge branch 'worktree-agent-a1398e81ad1a64c5f'508 // It wakes nobody, @mentions or not: colleagues get work only through hand_off.
509 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 asked510 return {
511 text: answer.text,
512 tokens: addTokens(answer.tokens, consulted.tokens),
513 cost: (model.ownModel ? 0 : answer.cost) + consulted.cost,
514 rounds: answer.rounds,
515 };
Chat and workspace agents: channels, DMs and named agents you talk to516 });
517
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 asked518 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.519 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 asked520 if (!done.value.text) {
521 const apology = await surface.post(APOLOGY).catch(() => null);
522 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 to523 }
524 await finish({ status: "replied", reply_id: posted, ...usage });
525 } catch (error) {
526 const message = error instanceof Error ? error.message : String(error);
527 console.error("agents: a reply failed", row.id, delivery.message_id, message);
528 const posted = await surface.post(APOLOGY).catch(() => null);
529 await finish({ status: "failed", error: message, reply_id: posted, ...usage }).catch((failure: unknown) =>
530 console.error("agents: a failed reply was not recorded", id, String(failure)),
531 );
532 }
533}

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