Skip to content
253 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";
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.9import { readLook } from "../../../packages/contracts/src/agent-look.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.10import { 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 to11
12export type Row = {
13 id: string;
14 workspace_id: string;
15 handle: string;
16 display_name: string;
17 avatar: string | null;
18 role: string;
19 instructions: string;
20 personality_preset: string;
21 personality: string;
22 routing: string;
23 budget: string;
24 autonomy: string;
25 capacity: number;
26 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)27 avatar_seed: string | null;
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.28 /** Its chosen face (JSON, @g1t/contracts agent-look.ts); null or missing before looks. */
29 look?: 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)30 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.31 /**
32 * Unused columns from when an agent carried its own team and department.
33 * Teams are memberships in identity now; `team` is read once, by the move
34 * to them (team-move.ts), and `role` made from them follows the title.
35 */
36 team?: string | null;
37 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)38 responsibilities: string | null;
39 subagents: string | null;
40 faces: string | null;
Agents recall what Docs say before they answer or work, and each has required reading41 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.42 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.43 /** Its abilities (JSON); missing before abilities. */
44 abilities?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to45 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)46 /** 1 for the workspace's built-in @g1t. */
47 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.48 /** `workspace`, or `personal` (a member's own); missing before personal agents. */
49 scope?: string | null;
50 /** A personal agent's member: user id and username. */
51 owner_id?: string | null;
52 owner_username?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to53 busy_until: string | null;
54 created_by: string;
55 created_at: string;
56 updated_at: string;
57 archived_at: string | null;
An archived agent can be found, brought back, or deleted for good. The Agents page ends with Archived, folded, where owners see every archived agent and members their own personal ones, each with when and by whom it was archived and what deleting it would take; Restore brings it back, under a new handle when a live agent has taken its old one, and Delete permanently asks for the handle to be typed and then removes its sessions, replies, memories, routines, versions, drafts, skills and its computer's home, leaving its messages in chat under its saved name, the workspace's spend totals and the audit log. In Chat an archived agent is deactivated the way people expect: never offered as someone to message or mention, never woken, a conversation with it kept in the list, dimmed and marked Archived, open to read with a note in place of the composer and a way to restore it for owners; after deletion the conversation still reads, its messages marked as a deleted agent's. The agents, chat and agent budgets guides say so.58 /** Who archived it, by username; missing before 0013. */
59 archived_by?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to60 /** Joined from agent_spend: this month and today. */
61 spent_month?: number | null;
62 spent_day?: number | null;
63};
64
65/**
66 * Agents with this month's and today's spend: `where` filters `agents`
67 * (aliased `a`); its parameters follow the two periods.
68 */
69export function selectAgents(where: string): string {
70 return `SELECT a.*, m.micros AS spent_month, d.micros AS spent_day FROM agents a
71 LEFT JOIN agent_spend m ON m.agent_id = a.id AND m.period = ?1
72 LEFT JOIN agent_spend d ON d.agent_id = a.id AND d.period = ?2
73 WHERE ${where}`;
74}
75
76export function periods(now: Date): [string, string] {
77 return [monthKey(now), dayKey(now)];
78}
79
80export 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.81 const title = row.title ?? "";
82 // A role made from the title and its old team or department is the title alone.
83 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 to84 return {
85 handle: row.handle,
86 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.87 role: made && title.trim() ? title.trim() : row.role,
Chat and workspace agents: channels, DMs and named agents you talk to88 instructions: row.instructions,
89 personality_preset: PRESETS.includes(row.personality_preset as Definition["personality_preset"])
90 ? (row.personality_preset as Definition["personality_preset"])
91 : "crisp",
92 personality: row.personality,
93 routing: readJson(row.routing, DEFAULT_ROUTING),
94 budget: readJson(row.budget, DEFAULT_BUDGET),
95 autonomy: readJson(row.autonomy, DEFAULT_AUTONOMY),
96 capacity: row.capacity,
97 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)98 avatar_seed: row.avatar_seed || row.handle,
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.99 look: readLook(row.look ?? 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.100 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)101 responsibilities: readList<string>(row.responsibilities),
102 subagents: readList<SubagentDef>(row.subagents),
103 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading104 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.105 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.106 abilities: abilitiesRead(row.abilities ?? null),
Chat and workspace agents: channels, DMs and named agents you talk to107 };
108}
109
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.110/** A stored abilities column, read defensively: settings by id and the MCP servers, each a list or an object or nothing. */
111function abilitiesRead(raw: string | null): AgentAbilities {
112 const read = readJson<Partial<AgentAbilities>>(raw, {});
113 return {
114 settings: read.settings && typeof read.settings === "object" && !Array.isArray(read.settings) ? read.settings : {},
115 mcp_servers: Array.isArray(read.mcp_servers) ? read.mcp_servers : EMPTY_ABILITIES.mcp_servers,
116 };
117}
118
Chat and workspace agents: channels, DMs and named agents you talk to119export function toAgent(row: Row, now: Date): WorkspaceAgent {
120 const definition = definitionOf(row);
121 const spent = { month: row.spent_month ?? 0, day: row.spent_day ?? 0 };
122 return {
123 id: row.id,
124 workspace_id: row.workspace_id,
125 avatar: row.avatar,
126 ...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)127 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.128 scope: isPersonal(row) ? "personal" : "workspace",
129 personal_owner_id: isPersonal(row) ? (row.owner_id ?? null) : null,
130 personal_owner: isPersonal(row) ? (row.owner_username ?? null) : null,
Chat and workspace agents: channels, DMs and named agents you talk to131 version: row.version,
132 status: row.archived_at ? "paused" : agentStatus({ busyUntil: row.busy_until, now, blocked: budgetBlock(definition.budget, spent, now) !== null }),
133 spent_month_micros: spent.month,
134 created_by: row.created_by,
135 created_at: row.created_at,
136 updated_at: row.updated_at,
137 archived_at: row.archived_at,
An archived agent can be found, brought back, or deleted for good. The Agents page ends with Archived, folded, where owners see every archived agent and members their own personal ones, each with when and by whom it was archived and what deleting it would take; Restore brings it back, under a new handle when a live agent has taken its old one, and Delete permanently asks for the handle to be typed and then removes its sessions, replies, memories, routines, versions, drafts, skills and its computer's home, leaving its messages in chat under its saved name, the workspace's spend totals and the audit log. In Chat an archived agent is deactivated the way people expect: never offered as someone to message or mention, never woken, a conversation with it kept in the list, dimmed and marked Archived, open to read with a note in place of the composer and a way to restore it for owners; after deletion the conversation still reads, its messages marked as a deleted agent's. The agents, chat and agent budgets guides say so.138 archived_by: row.archived_by ?? null,
Chat and workspace agents: channels, DMs and named agents you talk to139 };
140}
141
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.142/** Whether a row is a member's personal agent. */
143export function isPersonal(row: Pick<Row, "scope">): boolean {
144 return row.scope === "personal";
145}
146
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 asked147/**
148 * Adds a charge to the paying agent's month and day: a reply's, or a
149 * session step's (`task`), counted as one of each.
150 */
151export function spendStatements(db: D1Database, agentId: string, micros: number, now: Date, task: "reply" | "session" = "reply"): D1PreparedStatement[] {
152 const [replies, sessions] = task === "reply" ? [1, 0] : [0, 1];
Chat and workspace agents: channels, DMs and named agents you talk to153 return periods(now).map((period) =>
154 db
155 .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 asked156 `INSERT INTO agent_spend (agent_id, period, micros, replies, sessions) VALUES (?1, ?2, ?3, ?4, ?5)
157 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 to158 )
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 asked159 .bind(agentId, period, Math.max(0, Math.ceil(micros)), replies, sessions),
Chat and workspace agents: channels, DMs and named agents you talk to160 );
161}
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)162
163/** A stored JSON list, read defensively: anything else is empty. */
164function readList<T>(raw: string | null): T[] {
165 if (!raw) return [];
166 try {
167 const parsed = JSON.parse(raw) as unknown;
168 return Array.isArray(parsed) ? (parsed as T[]) : [];
169 } catch {
170 return [];
171 }
172}
173
174/**
175 * The agents table's definition columns, in the order `definitionColumns`
176 * gives their values. Statements are built from this one list, so a new
177 * field is added in one place.
178 */
179export const DEFINITION_COLUMNS = [
180 "handle",
181 "display_name",
182 "role",
183 "instructions",
184 "personality_preset",
185 "personality",
186 "routing",
187 "budget",
188 "autonomy",
189 "capacity",
190 "template",
191 "avatar_seed",
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.192 "look",
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)193 "title",
194 "responsibilities",
195 "subagents",
196 "faces",
Agents recall what Docs say before they answer or work, and each has required reading197 "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.198 "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.199 "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)200] as const;
201
202/** A definition's values, in `DEFINITION_COLUMNS` order. */
203export function definitionColumns(d: Definition): (string | number | null)[] {
204 return [
205 d.handle,
206 d.display_name,
207 d.role,
208 d.instructions,
209 d.personality_preset,
210 d.personality,
211 JSON.stringify(d.routing),
212 JSON.stringify(d.budget),
213 JSON.stringify(d.autonomy),
214 d.capacity,
215 d.template,
216 d.avatar_seed,
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.217 d.look ? JSON.stringify(d.look) : 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)218 d.title,
219 JSON.stringify(d.responsibilities),
220 JSON.stringify(d.subagents),
221 d.faces,
Agents recall what Docs say before they answer or work, and each has required reading222 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.223 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.224 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)225 ];
226}
227
228/**
229 * A new agent row: `id`, `workspace_id`, the definition, then `extra`
230 * columns (version, builtin, who made it, when), all bound in order.
231 */
232export function insertAgent(db: D1Database, id: string, workspaceId: string, d: Definition, extra: Record<string, string | number>, orIgnore = false): D1PreparedStatement {
233 const names = ["id", "workspace_id", ...DEFINITION_COLUMNS, ...Object.keys(extra)];
234 const values = [id, workspaceId, ...definitionColumns(d), ...Object.values(extra)];
235 return db
236 .prepare(`INSERT ${orIgnore ? "OR IGNORE " : ""}INTO agents (${names.join(", ")}) VALUES (${names.map(() => "?").join(", ")})`)
237 .bind(...values);
238}
239
240/** Sets a row's definition, from the version read only, with `extra` columns. */
241export function updateAgent(db: D1Database, id: string, readVersion: number, d: Definition, extra: Record<string, string | number>): D1PreparedStatement {
242 const names = [...DEFINITION_COLUMNS, ...Object.keys(extra)];
243 return db
244 .prepare(`UPDATE agents SET ${names.map((name) => `${name} = ?`).join(", ")} WHERE id = ? AND version = ?`)
245 .bind(...definitionColumns(d), ...Object.values(extra), id, readVersion);
246}
247
248/** A version of a definition, as saved. */
249export function versionStatement(db: D1Database, agentId: string, version: number, d: Definition, by: string, at: string): D1PreparedStatement {
250 return db
251 .prepare("INSERT INTO agent_versions (agent_id, version, definition, changed_by, created_at) VALUES (?, ?, ?, ?, ?)")
252 .bind(agentId, version, JSON.stringify(d), by, at);
253}

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