Skip to content
210 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;
Chat and workspace agents: channels, DMs and named agents you talk to33 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)34 /** 1 for the workspace's built-in @g1t. */
35 builtin: number;
Chat and workspace agents: channels, DMs and named agents you talk to36 busy_until: string | null;
37 created_by: string;
38 created_at: string;
39 updated_at: string;
40 archived_at: string | null;
41 /** Joined from agent_spend: this month and today. */
42 spent_month?: number | null;
43 spent_day?: number | null;
44};
45
46/**
47 * Agents with this month's and today's spend: `where` filters `agents`
48 * (aliased `a`); its parameters follow the two periods.
49 */
50export function selectAgents(where: string): string {
51 return `SELECT a.*, m.micros AS spent_month, d.micros AS spent_day FROM agents a
52 LEFT JOIN agent_spend m ON m.agent_id = a.id AND m.period = ?1
53 LEFT JOIN agent_spend d ON d.agent_id = a.id AND d.period = ?2
54 WHERE ${where}`;
55}
56
57export function periods(now: Date): [string, string] {
58 return [monthKey(now), dayKey(now)];
59}
60
61export function definitionOf(row: Row): Definition {
62 return {
63 handle: row.handle,
64 display_name: row.display_name,
65 role: row.role,
66 instructions: row.instructions,
67 personality_preset: PRESETS.includes(row.personality_preset as Definition["personality_preset"])
68 ? (row.personality_preset as Definition["personality_preset"])
69 : "crisp",
70 personality: row.personality,
71 routing: readJson(row.routing, DEFAULT_ROUTING),
72 budget: readJson(row.budget, DEFAULT_BUDGET),
73 autonomy: readJson(row.autonomy, DEFAULT_AUTONOMY),
74 capacity: row.capacity,
75 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)76 avatar_seed: row.avatar_seed || row.handle,
77 title: row.title ?? "",
78 team: row.team ?? null,
79 department: row.department ?? "",
80 responsibilities: readList<string>(row.responsibilities),
81 subagents: readList<SubagentDef>(row.subagents),
82 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading83 reading: readList<string>(row.reading ?? null),
Chat and workspace agents: channels, DMs and named agents you talk to84 };
85}
86
87export function toAgent(row: Row, now: Date): WorkspaceAgent {
88 const definition = definitionOf(row);
89 const spent = { month: row.spent_month ?? 0, day: row.spent_day ?? 0 };
90 return {
91 id: row.id,
92 workspace_id: row.workspace_id,
93 avatar: row.avatar,
94 ...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)95 builtin: !!row.builtin,
Chat and workspace agents: channels, DMs and named agents you talk to96 version: row.version,
97 status: row.archived_at ? "paused" : agentStatus({ busyUntil: row.busy_until, now, blocked: budgetBlock(definition.budget, spent, now) !== null }),
98 spent_month_micros: spent.month,
99 created_by: row.created_by,
100 created_at: row.created_at,
101 updated_at: row.updated_at,
102 archived_at: row.archived_at,
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/**
107 * Adds a charge to the paying agent's month and day: a reply's, or a
108 * session step's (`task`), counted as one of each.
109 */
110export function spendStatements(db: D1Database, agentId: string, micros: number, now: Date, task: "reply" | "session" = "reply"): D1PreparedStatement[] {
111 const [replies, sessions] = task === "reply" ? [1, 0] : [0, 1];
Chat and workspace agents: channels, DMs and named agents you talk to112 return periods(now).map((period) =>
113 db
114 .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 asked115 `INSERT INTO agent_spend (agent_id, period, micros, replies, sessions) VALUES (?1, ?2, ?3, ?4, ?5)
116 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 to117 )
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 asked118 .bind(agentId, period, Math.max(0, Math.ceil(micros)), replies, sessions),
Chat and workspace agents: channels, DMs and named agents you talk to119 );
120}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)121
122/** A stored JSON list, read defensively: anything else is empty. */
123function readList<T>(raw: string | null): T[] {
124 if (!raw) return [];
125 try {
126 const parsed = JSON.parse(raw) as unknown;
127 return Array.isArray(parsed) ? (parsed as T[]) : [];
128 } catch {
129 return [];
130 }
131}
132
133/**
134 * The agents table's definition columns, in the order `definitionColumns`
135 * gives their values. Statements are built from this one list, so a new
136 * field is added in one place.
137 */
138export const DEFINITION_COLUMNS = [
139 "handle",
140 "display_name",
141 "role",
142 "instructions",
143 "personality_preset",
144 "personality",
145 "routing",
146 "budget",
147 "autonomy",
148 "capacity",
149 "template",
150 "avatar_seed",
151 "title",
152 "team",
153 "department",
154 "responsibilities",
155 "subagents",
156 "faces",
Agents recall what Docs say before they answer or work, and each has required reading157 "reading",
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)158] as const;
159
160/** A definition's values, in `DEFINITION_COLUMNS` order. */
161export function definitionColumns(d: Definition): (string | number | null)[] {
162 return [
163 d.handle,
164 d.display_name,
165 d.role,
166 d.instructions,
167 d.personality_preset,
168 d.personality,
169 JSON.stringify(d.routing),
170 JSON.stringify(d.budget),
171 JSON.stringify(d.autonomy),
172 d.capacity,
173 d.template,
174 d.avatar_seed,
175 d.title,
176 d.team,
177 d.department,
178 JSON.stringify(d.responsibilities),
179 JSON.stringify(d.subagents),
180 d.faces,
Agents recall what Docs say before they answer or work, and each has required reading181 JSON.stringify(d.reading ?? []),
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)182 ];
183}
184
185/**
186 * A new agent row: `id`, `workspace_id`, the definition, then `extra`
187 * columns (version, builtin, who made it, when), all bound in order.
188 */
189export function insertAgent(db: D1Database, id: string, workspaceId: string, d: Definition, extra: Record<string, string | number>, orIgnore = false): D1PreparedStatement {
190 const names = ["id", "workspace_id", ...DEFINITION_COLUMNS, ...Object.keys(extra)];
191 const values = [id, workspaceId, ...definitionColumns(d), ...Object.values(extra)];
192 return db
193 .prepare(`INSERT ${orIgnore ? "OR IGNORE " : ""}INTO agents (${names.join(", ")}) VALUES (${names.map(() => "?").join(", ")})`)
194 .bind(...values);
195}
196
197/** Sets a row's definition, from the version read only, with `extra` columns. */
198export function updateAgent(db: D1Database, id: string, readVersion: number, d: Definition, extra: Record<string, string | number>): D1PreparedStatement {
199 const names = [...DEFINITION_COLUMNS, ...Object.keys(extra)];
200 return db
201 .prepare(`UPDATE agents SET ${names.map((name) => `${name} = ?`).join(", ")} WHERE id = ? AND version = ?`)
202 .bind(...definitionColumns(d), ...Object.values(extra), id, readVersion);
203}
204
205/** A version of a definition, as saved. */
206export function versionStatement(db: D1Database, agentId: string, version: number, d: Definition, by: string, at: string): D1PreparedStatement {
207 return db
208 .prepare("INSERT INTO agent_versions (agent_id, version, definition, changed_by, created_at) VALUES (?, ?, ?, ?, ?)")
209 .bind(agentId, version, JSON.stringify(d), by, at);
210}

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