Skip to content
244 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 * Agents as stored in D1, and their spend. Shared by the RPC methods and
3 * the desk.
4 */
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.5import type { AgentAbilities, SubagentDef, WorkspaceAgent } from "@g1t/contracts";
Chat and workspace agents: channels, DMs and named agents you talk to6
7import { agentStatus, budgetBlock, dayKey, monthKey } from "./budget.ts";
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.8import { EMPTY_ABILITIES } from "../../../packages/contracts/src/abilities.ts";
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.9import { type Definition, DEFAULT_AUTONOMY, DEFAULT_BUDGET, DEFAULT_ROUTING, legacyRoleOf, PRESETS, readJson } from "./definition.ts";
Chat and workspace agents: channels, DMs and named agents you talk to10
11export type Row = {
12 id: string;
13 workspace_id: string;
14 handle: string;
15 display_name: string;
16 avatar: string | null;
17 role: string;
18 instructions: string;
19 personality_preset: string;
20 personality: string;
21 routing: string;
22 budget: string;
23 autonomy: string;
24 capacity: number;
25 template: string | 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)26 avatar_seed: string | null;
27 title: string | null;
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.28 /**
29 * Unused columns from when an agent carried its own team and department.
30 * Teams are memberships in identity now; `team` is read once, by the move
31 * to them (team-move.ts), and `role` made from them follows the title.
32 */
33 team?: string | null;
34 department?: string | 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)35 responsibilities: string | null;
36 subagents: string | null;
37 faces: string | null;
Agents recall what Docs say before they answer or work, and each has required reading38 reading?: string | 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.39 skills_off?: string | null;
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.40 /** Its abilities (JSON); missing before abilities. */
41 abilities?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to42 version: number;
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)43 /** 1 for the workspace's built-in @g1t. */
44 builtin: number;
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.45 /** `workspace`, or `personal` (a member's own); missing before personal agents. */
46 scope?: string | null;
47 /** A personal agent's member: user id and username. */
48 owner_id?: string | null;
49 owner_username?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to50 busy_until: string | null;
51 created_by: string;
52 created_at: string;
53 updated_at: string;
54 archived_at: string | null;
55 /** Joined from agent_spend: this month and today. */
56 spent_month?: number | null;
57 spent_day?: number | null;
58};
59
60/**
61 * Agents with this month's and today's spend: `where` filters `agents`
62 * (aliased `a`); its parameters follow the two periods.
63 */
64export function selectAgents(where: string): string {
65 return `SELECT a.*, m.micros AS spent_month, d.micros AS spent_day FROM agents a
66 LEFT JOIN agent_spend m ON m.agent_id = a.id AND m.period = ?1
67 LEFT JOIN agent_spend d ON d.agent_id = a.id AND d.period = ?2
68 WHERE ${where}`;
69}
70
71export function periods(now: Date): [string, string] {
72 return [monthKey(now), dayKey(now)];
73}
74
75export function definitionOf(row: Row): Definition {
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.76 const title = row.title ?? "";
77 // A role made from the title and its old team or department is the title alone.
78 const made = (row.team || row.department?.trim()) && row.role === legacyRoleOf(title, row.team ?? null, row.department ?? null);
Chat and workspace agents: channels, DMs and named agents you talk to79 return {
80 handle: row.handle,
81 display_name: row.display_name,
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.82 role: made && title.trim() ? title.trim() : row.role,
Chat and workspace agents: channels, DMs and named agents you talk to83 instructions: row.instructions,
84 personality_preset: PRESETS.includes(row.personality_preset as Definition["personality_preset"])
85 ? (row.personality_preset as Definition["personality_preset"])
86 : "crisp",
87 personality: row.personality,
88 routing: readJson(row.routing, DEFAULT_ROUTING),
89 budget: readJson(row.budget, DEFAULT_BUDGET),
90 autonomy: readJson(row.autonomy, DEFAULT_AUTONOMY),
91 capacity: row.capacity,
92 template: row.template,
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)93 avatar_seed: row.avatar_seed || row.handle,
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.94 title,
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)95 responsibilities: readList<string>(row.responsibilities),
96 subagents: readList<SubagentDef>(row.subagents),
97 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading98 reading: readList<string>(row.reading ?? 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.99 skills_off: readList<string>(row.skills_off ?? null),
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.100 abilities: abilitiesRead(row.abilities ?? null),
Chat and workspace agents: channels, DMs and named agents you talk to101 };
102}
103
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.104/** A stored abilities column, read defensively: settings by id and the MCP servers, each a list or an object or nothing. */
105function abilitiesRead(raw: string | null): AgentAbilities {
106 const read = readJson<Partial<AgentAbilities>>(raw, {});
107 return {
108 settings: read.settings && typeof read.settings === "object" && !Array.isArray(read.settings) ? read.settings : {},
109 mcp_servers: Array.isArray(read.mcp_servers) ? read.mcp_servers : EMPTY_ABILITIES.mcp_servers,
110 };
111}
112
Chat and workspace agents: channels, DMs and named agents you talk to113export function toAgent(row: Row, now: Date): WorkspaceAgent {
114 const definition = definitionOf(row);
115 const spent = { month: row.spent_month ?? 0, day: row.spent_day ?? 0 };
116 return {
117 id: row.id,
118 workspace_id: row.workspace_id,
119 avatar: row.avatar,
120 ...definition,
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 builtin: !!row.builtin,
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.122 scope: isPersonal(row) ? "personal" : "workspace",
123 personal_owner_id: isPersonal(row) ? (row.owner_id ?? null) : null,
124 personal_owner: isPersonal(row) ? (row.owner_username ?? null) : null,
Chat and workspace agents: channels, DMs and named agents you talk to125 version: row.version,
126 status: row.archived_at ? "paused" : agentStatus({ busyUntil: row.busy_until, now, blocked: budgetBlock(definition.budget, spent, now) !== null }),
127 spent_month_micros: spent.month,
128 created_by: row.created_by,
129 created_at: row.created_at,
130 updated_at: row.updated_at,
131 archived_at: row.archived_at,
132 };
133}
134
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.135/** Whether a row is a member's personal agent. */
136export function isPersonal(row: Pick<Row, "scope">): boolean {
137 return row.scope === "personal";
138}
139
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 asked140/**
141 * Adds a charge to the paying agent's month and day: a reply's, or a
142 * session step's (`task`), counted as one of each.
143 */
144export function spendStatements(db: D1Database, agentId: string, micros: number, now: Date, task: "reply" | "session" = "reply"): D1PreparedStatement[] {
145 const [replies, sessions] = task === "reply" ? [1, 0] : [0, 1];
Chat and workspace agents: channels, DMs and named agents you talk to146 return periods(now).map((period) =>
147 db
148 .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 asked149 `INSERT INTO agent_spend (agent_id, period, micros, replies, sessions) VALUES (?1, ?2, ?3, ?4, ?5)
150 ON CONFLICT (agent_id, period) DO UPDATE SET micros = micros + ?3, replies = replies + ?4, sessions = sessions + ?5`,
Chat and workspace agents: channels, DMs and named agents you talk to151 )
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 asked152 .bind(agentId, period, Math.max(0, Math.ceil(micros)), replies, sessions),
Chat and workspace agents: channels, DMs and named agents you talk to153 );
154}
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)155
156/** A stored JSON list, read defensively: anything else is empty. */
157function readList<T>(raw: string | null): T[] {
158 if (!raw) return [];
159 try {
160 const parsed = JSON.parse(raw) as unknown;
161 return Array.isArray(parsed) ? (parsed as T[]) : [];
162 } catch {
163 return [];
164 }
165}
166
167/**
168 * The agents table's definition columns, in the order `definitionColumns`
169 * gives their values. Statements are built from this one list, so a new
170 * field is added in one place.
171 */
172export const DEFINITION_COLUMNS = [
173 "handle",
174 "display_name",
175 "role",
176 "instructions",
177 "personality_preset",
178 "personality",
179 "routing",
180 "budget",
181 "autonomy",
182 "capacity",
183 "template",
184 "avatar_seed",
185 "title",
186 "responsibilities",
187 "subagents",
188 "faces",
Agents recall what Docs say before they answer or work, and each has required reading189 "reading",
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.190 "skills_off",
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.191 "abilities",
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)192] as const;
193
194/** A definition's values, in `DEFINITION_COLUMNS` order. */
195export function definitionColumns(d: Definition): (string | number | null)[] {
196 return [
197 d.handle,
198 d.display_name,
199 d.role,
200 d.instructions,
201 d.personality_preset,
202 d.personality,
203 JSON.stringify(d.routing),
204 JSON.stringify(d.budget),
205 JSON.stringify(d.autonomy),
206 d.capacity,
207 d.template,
208 d.avatar_seed,
209 d.title,
210 JSON.stringify(d.responsibilities),
211 JSON.stringify(d.subagents),
212 d.faces,
Agents recall what Docs say before they answer or work, and each has required reading213 JSON.stringify(d.reading ?? []),
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.214 JSON.stringify(d.skills_off ?? []),
Each agent has an Abilities tab: g1t's built-ins, always on within the asker's access; its computer, coming; each connected integration's actions one row each, read, import, comment or resolve, with a level for each, alone, alone when the person asked for it, ask first or never, and whose connection it runs on; and MCP servers an owner adds. Reading is alone, writing inside g1t follows today's choices, anything that leaves g1t asks first, and production deploys can't go above ask. The agents service enforces every level: ask first posts a card to allow or deny and parks a session until it's answered, a refusal names its rule in the transcript and the audit log, and a missing ability posts a request to owners. The agent abilities guide says how.215 JSON.stringify(d.abilities ?? EMPTY_ABILITIES),
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)216 ];
217}
218
219/**
220 * A new agent row: `id`, `workspace_id`, the definition, then `extra`
221 * columns (version, builtin, who made it, when), all bound in order.
222 */
223export function insertAgent(db: D1Database, id: string, workspaceId: string, d: Definition, extra: Record<string, string | number>, orIgnore = false): D1PreparedStatement {
224 const names = ["id", "workspace_id", ...DEFINITION_COLUMNS, ...Object.keys(extra)];
225 const values = [id, workspaceId, ...definitionColumns(d), ...Object.values(extra)];
226 return db
227 .prepare(`INSERT ${orIgnore ? "OR IGNORE " : ""}INTO agents (${names.join(", ")}) VALUES (${names.map(() => "?").join(", ")})`)
228 .bind(...values);
229}
230
231/** Sets a row's definition, from the version read only, with `extra` columns. */
232export function updateAgent(db: D1Database, id: string, readVersion: number, d: Definition, extra: Record<string, string | number>): D1PreparedStatement {
233 const names = [...DEFINITION_COLUMNS, ...Object.keys(extra)];
234 return db
235 .prepare(`UPDATE agents SET ${names.map((name) => `${name} = ?`).join(", ")} WHERE id = ? AND version = ?`)
236 .bind(...definitionColumns(d), ...Object.values(extra), id, readVersion);
237}
238
239/** A version of a definition, as saved. */
240export function versionStatement(db: D1Database, agentId: string, version: number, d: Definition, by: string, at: string): D1PreparedStatement {
241 return db
242 .prepare("INSERT INTO agent_versions (agent_id, version, definition, changed_by, created_at) VALUES (?, ?, ?, ?, ?)")
243 .bind(agentId, version, JSON.stringify(d), by, at);
244}

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