Skip to content
227 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 */
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)5import type { 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";
8import { type Definition, DEFAULT_AUTONOMY, DEFAULT_BUDGET, DEFAULT_ROUTING, PRESETS, readJson } from "./definition.ts";
9
10export type Row = {
11 id: string;
12 workspace_id: string;
13 handle: string;
14 display_name: string;
15 avatar: string | null;
16 role: string;
17 instructions: string;
18 personality_preset: string;
19 personality: string;
20 routing: string;
21 budget: string;
22 autonomy: string;
23 capacity: number;
24 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)25 avatar_seed: string | null;
26 title: string | null;
27 team: string | null;
28 department: string | null;
29 responsibilities: string | null;
30 subagents: string | null;
31 faces: string | null;
Agents recall what Docs say before they answer or work, and each has required reading32 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.33 skills_off?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to34 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)35 /** 1 for the workspace's built-in @g1t. */
36 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.37 /** `workspace`, or `personal` (a member's own); missing before personal agents. */
38 scope?: string | null;
39 /** A personal agent's member: user id and username. */
40 owner_id?: string | null;
41 owner_username?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to42 busy_until: string | null;
43 created_by: string;
44 created_at: string;
45 updated_at: string;
46 archived_at: string | null;
47 /** Joined from agent_spend: this month and today. */
48 spent_month?: number | null;
49 spent_day?: number | null;
50};
51
52/**
53 * Agents with this month's and today's spend: `where` filters `agents`
54 * (aliased `a`); its parameters follow the two periods.
55 */
56export function selectAgents(where: string): string {
57 return `SELECT a.*, m.micros AS spent_month, d.micros AS spent_day FROM agents a
58 LEFT JOIN agent_spend m ON m.agent_id = a.id AND m.period = ?1
59 LEFT JOIN agent_spend d ON d.agent_id = a.id AND d.period = ?2
60 WHERE ${where}`;
61}
62
63export function periods(now: Date): [string, string] {
64 return [monthKey(now), dayKey(now)];
65}
66
67export function definitionOf(row: Row): Definition {
68 return {
69 handle: row.handle,
70 display_name: row.display_name,
71 role: row.role,
72 instructions: row.instructions,
73 personality_preset: PRESETS.includes(row.personality_preset as Definition["personality_preset"])
74 ? (row.personality_preset as Definition["personality_preset"])
75 : "crisp",
76 personality: row.personality,
77 routing: readJson(row.routing, DEFAULT_ROUTING),
78 budget: readJson(row.budget, DEFAULT_BUDGET),
79 autonomy: readJson(row.autonomy, DEFAULT_AUTONOMY),
80 capacity: row.capacity,
81 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)82 avatar_seed: row.avatar_seed || row.handle,
83 title: row.title ?? "",
84 team: row.team ?? null,
85 department: row.department ?? "",
86 responsibilities: readList<string>(row.responsibilities),
87 subagents: readList<SubagentDef>(row.subagents),
88 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading89 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.90 skills_off: readList<string>(row.skills_off ?? null),
Chat and workspace agents: channels, DMs and named agents you talk to91 };
92}
93
94export function toAgent(row: Row, now: Date): WorkspaceAgent {
95 const definition = definitionOf(row);
96 const spent = { month: row.spent_month ?? 0, day: row.spent_day ?? 0 };
97 return {
98 id: row.id,
99 workspace_id: row.workspace_id,
100 avatar: row.avatar,
101 ...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)102 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.103 scope: isPersonal(row) ? "personal" : "workspace",
104 personal_owner_id: isPersonal(row) ? (row.owner_id ?? null) : null,
105 personal_owner: isPersonal(row) ? (row.owner_username ?? null) : null,
Chat and workspace agents: channels, DMs and named agents you talk to106 version: row.version,
107 status: row.archived_at ? "paused" : agentStatus({ busyUntil: row.busy_until, now, blocked: budgetBlock(definition.budget, spent, now) !== null }),
108 spent_month_micros: spent.month,
109 created_by: row.created_by,
110 created_at: row.created_at,
111 updated_at: row.updated_at,
112 archived_at: row.archived_at,
113 };
114}
115
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.116/** Whether a row is a member's personal agent. */
117export function isPersonal(row: Pick<Row, "scope">): boolean {
118 return row.scope === "personal";
119}
120
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 asked121/**
122 * Adds a charge to the paying agent's month and day: a reply's, or a
123 * session step's (`task`), counted as one of each.
124 */
125export function spendStatements(db: D1Database, agentId: string, micros: number, now: Date, task: "reply" | "session" = "reply"): D1PreparedStatement[] {
126 const [replies, sessions] = task === "reply" ? [1, 0] : [0, 1];
Chat and workspace agents: channels, DMs and named agents you talk to127 return periods(now).map((period) =>
128 db
129 .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 asked130 `INSERT INTO agent_spend (agent_id, period, micros, replies, sessions) VALUES (?1, ?2, ?3, ?4, ?5)
131 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 to132 )
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 asked133 .bind(agentId, period, Math.max(0, Math.ceil(micros)), replies, sessions),
Chat and workspace agents: channels, DMs and named agents you talk to134 );
135}
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)136
137/** A stored JSON list, read defensively: anything else is empty. */
138function readList<T>(raw: string | null): T[] {
139 if (!raw) return [];
140 try {
141 const parsed = JSON.parse(raw) as unknown;
142 return Array.isArray(parsed) ? (parsed as T[]) : [];
143 } catch {
144 return [];
145 }
146}
147
148/**
149 * The agents table's definition columns, in the order `definitionColumns`
150 * gives their values. Statements are built from this one list, so a new
151 * field is added in one place.
152 */
153export const DEFINITION_COLUMNS = [
154 "handle",
155 "display_name",
156 "role",
157 "instructions",
158 "personality_preset",
159 "personality",
160 "routing",
161 "budget",
162 "autonomy",
163 "capacity",
164 "template",
165 "avatar_seed",
166 "title",
167 "team",
168 "department",
169 "responsibilities",
170 "subagents",
171 "faces",
Agents recall what Docs say before they answer or work, and each has required reading172 "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.173 "skills_off",
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)174] as const;
175
176/** A definition's values, in `DEFINITION_COLUMNS` order. */
177export function definitionColumns(d: Definition): (string | number | null)[] {
178 return [
179 d.handle,
180 d.display_name,
181 d.role,
182 d.instructions,
183 d.personality_preset,
184 d.personality,
185 JSON.stringify(d.routing),
186 JSON.stringify(d.budget),
187 JSON.stringify(d.autonomy),
188 d.capacity,
189 d.template,
190 d.avatar_seed,
191 d.title,
192 d.team,
193 d.department,
194 JSON.stringify(d.responsibilities),
195 JSON.stringify(d.subagents),
196 d.faces,
Agents recall what Docs say before they answer or work, and each has required reading197 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.198 JSON.stringify(d.skills_off ?? []),
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)199 ];
200}
201
202/**
203 * A new agent row: `id`, `workspace_id`, the definition, then `extra`
204 * columns (version, builtin, who made it, when), all bound in order.
205 */
206export function insertAgent(db: D1Database, id: string, workspaceId: string, d: Definition, extra: Record<string, string | number>, orIgnore = false): D1PreparedStatement {
207 const names = ["id", "workspace_id", ...DEFINITION_COLUMNS, ...Object.keys(extra)];
208 const values = [id, workspaceId, ...definitionColumns(d), ...Object.values(extra)];
209 return db
210 .prepare(`INSERT ${orIgnore ? "OR IGNORE " : ""}INTO agents (${names.join(", ")}) VALUES (${names.map(() => "?").join(", ")})`)
211 .bind(...values);
212}
213
214/** Sets a row's definition, from the version read only, with `extra` columns. */
215export function updateAgent(db: D1Database, id: string, readVersion: number, d: Definition, extra: Record<string, string | number>): D1PreparedStatement {
216 const names = [...DEFINITION_COLUMNS, ...Object.keys(extra)];
217 return db
218 .prepare(`UPDATE agents SET ${names.map((name) => `${name} = ?`).join(", ")} WHERE id = ? AND version = ?`)
219 .bind(...definitionColumns(d), ...Object.values(extra), id, readVersion);
220}
221
222/** A version of a definition, as saved. */
223export function versionStatement(db: D1Database, agentId: string, version: number, d: Definition, by: string, at: string): D1PreparedStatement {
224 return db
225 .prepare("INSERT INTO agent_versions (agent_id, version, definition, changed_by, created_at) VALUES (?, ?, ?, ?, ?)")
226 .bind(agentId, version, JSON.stringify(d), by, at);
227}

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