Skip to content
358 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 * An agent's definition: what a new one gets by default, what a change may
3 * set, and how a stored row reads. Pure, so it is tested on its own.
4 */
5import type {
6 AgentAutonomy,
7 AgentBudget,
8 AgentRouting,
9 NewWorkspaceAgent,
10 PersonalityPreset,
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)11 AgentFaces,
12 SubagentDef,
Chat and workspace agents: channels, DMs and named agents you talk to13} from "@g1t/contracts";
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.14import { FOUNDATIONAL_SKILL_IDS } from "../../../packages/contracts/src/skills.ts";
Chat and workspace agents: channels, DMs and named agents you talk to15
16import { checkHandle } from "./handle.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.17import { isEffort, isTier, limitsAgree } from "./routing.ts";
Chat and workspace agents: channels, DMs and named agents you talk to18
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.19/** A library skill's id (skill-library.ts), as `skills_off` may name it. */
20const LIBRARY_SKILL_ID = /^skl_[0-9a-z]{26}$/;
21
Chat and workspace agents: channels, DMs and named agents you talk to22export const PRESETS: PersonalityPreset[] = ["crisp", "friendly", "socratic", "terse"];
23
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.24export const DEFAULT_ROUTING: AgentRouting = { floor: null, ceiling: null, providers: [], pinned: null, effort: "auto" };
Chat and workspace agents: channels, DMs and named agents you talk to25export const DEFAULT_BUDGET: AgentBudget = { monthly_micros: null, daily_micros: null, task_micros: null };
26/** What an agent may do alone until someone says otherwise: open pull requests; the rest asks. */
27export const DEFAULT_AUTONOMY: AgentAutonomy = {
28 open_pull_requests: "alone",
29 merge: "approval",
30 deploy_production: "approval",
31 edit_docs: "suggest",
32};
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.33/** Tasks at once. */
Chat and workspace agents: channels, DMs and named agents you talk to34export const DEFAULT_CAPACITY = 3;
35export const MAX_CAPACITY = 10;
36
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)37const LIMITS = { displayName: 64, role: 120, title: 60, department: 40, duty: 160, instructions: 8000, personality: 1000, providers: 10, pinned: 200 };
Chat and workspace agents: channels, DMs and named agents you talk to38/** $100,000 in millionths: a cap above this is a typo. */
39const MAX_MICROS = 100_000_000_000;
40
41/** Everything a definition holds, complete: what is stored and versioned. */
42export type Definition = {
43 handle: string;
44 display_name: string;
45 role: string;
46 instructions: string;
47 personality_preset: PersonalityPreset;
48 personality: string;
49 routing: AgentRouting;
50 budget: AgentBudget;
51 autonomy: AgentAutonomy;
52 capacity: number;
53 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)54 /** What its generated avatar is drawn from. */
55 avatar_seed: string;
56 title: string;
57 team: string | null;
58 department: string;
59 responsibilities: string[];
60 subagents: SubagentDef[];
61 faces: AgentFaces;
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.62 /** Spaces whose artifacts it reads first. */
Agents recall what Docs say before they answer or work, and each has required reading63 reading: string[];
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.64 /** Foundational skills turned off for it, by id (@g1t/contracts skills.ts). */
65 skills_off: string[];
Chat and workspace agents: channels, DMs and named agents you talk to66};
67
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)68/** The one-line role a title and team (or department) make: "QA Engineer on the qa team". */
69export function roleOf(d: Pick<Definition, "title" | "team" | "department">): string {
70 const title = d.title.trim();
71 if (!title) return "";
72 if (d.team) return `${title} on the ${d.team} team`;
73 return d.department.trim() ? `${title}, ${d.department.trim()}` : title;
74}
75
76export const MAX_SUBAGENTS = 8;
77
Chat and workspace agents: channels, DMs and named agents you talk to78export type Checked<T> = { ok: true; value: T } | { ok: false; message: string };
79
80const bad = (message: string): { ok: false; message: string } => ({ ok: false, message });
81
82function text(value: unknown, what: string, max: number, required: boolean): Checked<string> {
83 if (value === undefined || value === null) return required ? bad(`${what} is required.`) : { ok: true, value: "" };
84 if (typeof value !== "string") return bad(`${what} is text.`);
85 const trimmed = value.trim();
86 if (required && !trimmed) return bad(`${what} is required.`);
87 if (trimmed.length > max) return bad(`${what} is at most ${max} characters.`);
88 return { ok: true, value: trimmed };
89}
90
91function routingOf(base: AgentRouting, given: unknown): Checked<AgentRouting> {
92 if (given === undefined || given === null) return { ok: true, value: base };
93 if (typeof given !== "object") return bad("Routing is an object.");
94 const g = given as Partial<AgentRouting>;
95 const next: AgentRouting = { ...base };
96 for (const key of ["floor", "ceiling"] as const) {
97 if (g[key] === undefined) continue;
98 if (g[key] !== null && !isTier(g[key])) return bad(`The ${key} is small, large, frontier or none.`);
99 next[key] = g[key] ?? null;
100 }
101 if (!limitsAgree(next.floor, next.ceiling)) return bad("The floor is above the ceiling: lower the floor or raise the ceiling.");
102 if (g.providers !== undefined) {
103 if (!Array.isArray(g.providers) || g.providers.some((p) => typeof p !== "string" || !p.trim())) return bad("Providers is a list of names.");
104 const providers = [...new Set(g.providers.map((p) => p.trim()))];
105 if (providers.length > LIMITS.providers) return bad(`An agent names at most ${LIMITS.providers} providers.`);
106 next.providers = providers;
107 }
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.108 if (g.effort !== undefined) {
109 if (g.effort !== null && !isEffort(g.effort)) return bad("Effort is auto, low, medium, high or max.");
110 next.effort = g.effort ?? "auto";
111 }
Chat and workspace agents: channels, DMs and named agents you talk to112 if (g.pinned !== undefined) {
113 if (g.pinned === null || g.pinned === "") next.pinned = null;
114 else if (typeof g.pinned !== "string" || g.pinned.trim().length > LIMITS.pinned || !/^[^/\s]+\/\S+$/.test(g.pinned.trim())) {
115 return bad("A pinned model is written provider/model.");
116 } else next.pinned = g.pinned.trim();
117 }
118 return { ok: true, value: next };
119}
120
121function budgetOf(base: AgentBudget, given: unknown): Checked<AgentBudget> {
122 if (given === undefined || given === null) return { ok: true, value: base };
123 if (typeof given !== "object") return bad("Budget is an object.");
124 const g = given as Partial<AgentBudget>;
125 const next: AgentBudget = { ...base };
126 for (const key of ["monthly_micros", "daily_micros", "task_micros"] as const) {
127 const value = g[key];
128 if (value === undefined) continue;
129 if (value === null) next[key] = null;
130 else if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > MAX_MICROS) {
131 return bad("A budget is a whole number of millionths of a dollar, or none.");
132 } else next[key] = value;
133 }
134 return { ok: true, value: next };
135}
136
137const AUTONOMY_CHOICES: { [K in keyof AgentAutonomy]: AgentAutonomy[K][] } = {
138 open_pull_requests: ["alone", "approval"],
139 merge: ["alone", "approval", "never"],
140 deploy_production: ["approval", "never"],
141 edit_docs: ["alone", "suggest"],
142};
143
144function autonomyOf(base: AgentAutonomy, given: unknown): Checked<AgentAutonomy> {
145 if (given === undefined || given === null) return { ok: true, value: base };
146 if (typeof given !== "object") return bad("Autonomy is an object.");
147 const next = { ...base } as Record<string, string>;
148 for (const [key, choices] of Object.entries(AUTONOMY_CHOICES) as [string, string[]][]) {
149 const value = (given as Record<string, unknown>)[key];
150 if (value === undefined) continue;
151 if (typeof value !== "string" || !choices.includes(value)) return bad(`${key} is one of ${choices.join(", ")}.`);
152 next[key] = value;
153 }
154 return { ok: true, value: next as AgentAutonomy };
155}
156
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)157function responsibilitiesOf(given: unknown): Checked<string[]> {
158 if (!Array.isArray(given) || given.some((d) => typeof d !== "string")) return bad("Responsibilities are a list of short duties.");
159 const duties = [...new Set((given as string[]).map((d) => d.trim()).filter(Boolean))];
160 if (duties.length && (duties.length < 2 || duties.length > 8)) return bad("Give 2 to 8 responsibilities, or none yet.");
161 if (duties.some((d) => d.length > LIMITS.duty)) return bad(`Each responsibility is at most ${LIMITS.duty} characters.`);
162 return { ok: true, value: duties };
163}
164
165const ORDER = ["small", "large", "frontier"] as const;
166
167/** A subagent's limits held within its agent's: never a lower floor, never a higher ceiling. */
168export function withinParent(
169 sub: { floor: AgentRouting["floor"]; ceiling: AgentRouting["ceiling"] },
170 parent: Pick<AgentRouting, "floor" | "ceiling">,
171): { floor: AgentRouting["floor"]; ceiling: AgentRouting["ceiling"] } {
172 const at = (tier: AgentRouting["floor"]) => (tier ? ORDER.indexOf(tier) : -1);
173 let floor = sub.floor;
174 let ceiling = sub.ceiling;
175 if (parent.floor && at(floor) < at(parent.floor)) floor = parent.floor;
176 if (parent.ceiling && (!ceiling || at(ceiling) > at(parent.ceiling))) ceiling = parent.ceiling;
177 // Held inside, a floor can end above the ceiling: the ceiling, the spending rail, wins.
178 if (floor && ceiling && at(floor) > at(ceiling)) floor = ceiling;
179 return { floor, ceiling };
180}
181
182function subagentsOf(given: unknown): Checked<SubagentDef[]> {
183 if (!Array.isArray(given)) return bad("Subagents are a list.");
184 if (given.length > MAX_SUBAGENTS) return bad(`An agent keeps at most ${MAX_SUBAGENTS} subagents.`);
185 const out: SubagentDef[] = [];
186 for (const raw of given as Partial<SubagentDef>[]) {
187 if (!raw || typeof raw !== "object") return bad("Each subagent has a name, a description and instructions.");
188 const name = typeof raw.name === "string" ? raw.name.trim().toLowerCase() : "";
189 if (!/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){1,31}$/.test(name)) return bad("A subagent's name is 2 to 32 lowercase letters, digits and single hyphens.");
190 if (out.some((sub) => sub.name === name)) return bad(`Two subagents are called ${name}.`);
191 const description = text(raw.description, `${name}'s description`, 200, true);
192 if (!description.ok) return description;
193 const instructions = text(raw.instructions, `${name}'s instructions`, 4000, true);
194 if (!instructions.ok) return instructions;
195 const routing = raw.routing ?? { floor: null, ceiling: null };
196 for (const tier of [routing.floor, routing.ceiling]) {
197 if (tier !== null && tier !== undefined && !isTier(tier)) return bad(`${name}'s limits are small, large, frontier or none.`);
198 }
199 if (!limitsAgree(routing.floor ?? null, routing.ceiling ?? null)) return bad(`${name}'s floor is above its ceiling.`);
200 const parallel = raw.max_parallel ?? 2;
201 if (typeof parallel !== "number" || !Number.isInteger(parallel) || parallel < 1 || parallel > 8) return bad(`${name} runs 1 to 8 at once.`);
202 out.push({
203 name,
204 description: description.value,
205 instructions: instructions.value,
206 routing: { floor: routing.floor ?? null, ceiling: routing.ceiling ?? null },
207 max_parallel: parallel,
208 });
209 }
210 return { ok: true, value: out };
211}
212
Chat and workspace agents: channels, DMs and named agents you talk to213/**
214 * `changes` applied to `base` (a new agent's defaults, or its current
215 * definition), checked. `templates` are the template ids an agent may name.
216 */
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)217export function applyChanges(
218 base: Definition | null,
219 changes: Partial<NewWorkspaceAgent>,
220 templates: string[],
221 options: { builtin?: boolean } = {},
222): Checked<Definition> {
Chat and workspace agents: channels, DMs and named agents you talk to223 if (!changes || typeof changes !== "object") return bad("Send the agent's fields.");
224 const creating = base === null;
225 const from: Definition = base ?? {
226 handle: "",
227 display_name: "",
228 role: "",
229 instructions: "",
230 personality_preset: "crisp",
231 personality: "",
232 routing: DEFAULT_ROUTING,
233 budget: DEFAULT_BUDGET,
234 autonomy: DEFAULT_AUTONOMY,
235 capacity: DEFAULT_CAPACITY,
236 template: 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)237 avatar_seed: "",
238 title: "",
239 team: null,
240 department: "",
241 responsibilities: [],
242 subagents: [],
243 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading244 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.245 skills_off: [],
Chat and workspace agents: channels, DMs and named agents you talk to246 };
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.247 const next: Definition = { ...from, skills_off: from.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)248 // Whether the role was made from the title and team, so it follows them.
249 const roleDerived = !from.role || from.role === roleOf(from);
Chat and workspace agents: channels, DMs and named agents you talk to250 if (creating || changes.handle !== undefined) {
251 const handle = checkHandle(changes.handle);
252 if (!handle.ok) return bad(handle.message);
253 next.handle = handle.handle;
254 }
255 const fields = [
256 ["display_name", "A display name", LIMITS.displayName, true],
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)257 ["role", "The role", LIMITS.role, false],
258 ["title", "The title", LIMITS.title, false],
259 ["department", "The department", LIMITS.department, false],
260 // The built-in agent's instructions are added to its fixed job, and may be empty.
261 ["instructions", "The instructions", LIMITS.instructions, !options.builtin],
Chat and workspace agents: channels, DMs and named agents you talk to262 ["personality", "The personality", LIMITS.personality, false],
263 ] as const;
264 for (const [key, what, max, required] of fields) {
265 if (!creating && changes[key] === undefined) continue;
266 const value = text(changes[key], what, max, required);
267 if (!value.ok) return value;
268 next[key] = value.value;
269 }
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)270 if (changes.team !== undefined) {
271 if (changes.team === null || changes.team === "") next.team = null;
272 else if (typeof changes.team !== "string" || !/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(changes.team.trim().toLowerCase())) {
273 return bad("A team is named by its slug.");
274 } else next.team = changes.team.trim().toLowerCase();
275 }
276 if (changes.responsibilities !== undefined) {
277 const duties = responsibilitiesOf(changes.responsibilities);
278 if (!duties.ok) return duties;
279 next.responsibilities = duties.value;
280 }
281 // A role left empty, or made from the title and team before, follows them.
282 if (!next.role || (changes.role === undefined && roleDerived)) next.role = roleOf(next);
283 if (!next.role) return bad("Give the agent a title or a one-line role.");
284 if (changes.avatar_seed !== undefined) {
285 const seed = text(changes.avatar_seed, "The avatar seed", 64, false);
286 if (!seed.ok) return seed;
287 next.avatar_seed = seed.value;
288 }
289 // A new face from the handle, unless one was chosen; renaming keeps the face.
290 if (!next.avatar_seed) next.avatar_seed = next.handle;
Chat and workspace agents: channels, DMs and named agents you talk to291 if (changes.personality_preset !== undefined) {
292 if (!PRESETS.includes(changes.personality_preset)) return bad(`The personality preset is one of ${PRESETS.join(", ")}.`);
293 next.personality_preset = changes.personality_preset;
294 }
295 const routing = routingOf(next.routing, changes.routing);
296 if (!routing.ok) return routing;
297 next.routing = routing.value;
298 const budget = budgetOf(next.budget, changes.budget);
299 if (!budget.ok) return budget;
300 next.budget = budget.value;
301 const autonomy = autonomyOf(next.autonomy, changes.autonomy);
302 if (!autonomy.ok) return autonomy;
303 next.autonomy = autonomy.value;
304 if (changes.capacity !== undefined) {
305 const capacity = changes.capacity;
306 if (typeof capacity !== "number" || !Number.isInteger(capacity) || capacity < 1 || capacity > MAX_CAPACITY) {
307 return bad(`Capacity is 1 to ${MAX_CAPACITY} tasks at once.`);
308 }
309 next.capacity = capacity;
310 }
311 if (changes.template !== undefined) {
312 if (changes.template !== null && !templates.includes(changes.template)) return bad("There is no such template.");
313 next.template = changes.template;
314 }
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)315 if (changes.subagents !== undefined) {
316 const subagents = subagentsOf(changes.subagents);
317 if (!subagents.ok) return subagents;
318 next.subagents = subagents.value;
319 }
Agents recall what Docs say before they answer or work, and each has required reading320 if (changes.reading !== undefined) {
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.321 if (!Array.isArray(changes.reading)) return bad("Required reading is a list of spaces.");
Agents recall what Docs say before they answer or work, and each has required reading322 const ids = [...new Set(changes.reading.filter((id): id is string => typeof id === "string").map((id) => id.trim()).filter(Boolean))];
323 if (ids.length > 10) return bad("An agent has at most 10 spaces of required reading.");
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.324 if (ids.some((id) => !/^[A-Za-z0-9_-]{1,80}$/.test(id))) return bad("That isn't a space.");
Agents recall what Docs say before they answer or work, and each has required reading325 next.reading = ids;
326 }
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.327 if (changes.skills_off !== undefined) {
328 if (!Array.isArray(changes.skills_off)) return bad("Skills turned off are a list of skills.");
329 const ids = new Set(changes.skills_off.filter((id): id is string => typeof id === "string").map((id) => id.trim()).filter(Boolean));
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.330 // Foundational skills by id, and the library's by theirs (skl_…), which
331 // may be off before or after they are attached.
332 const library = [...ids].filter((id) => LIBRARY_SKILL_ID.test(id));
333 const unknown = [...ids].find((id) => !FOUNDATIONAL_SKILL_IDS.includes(id) && !LIBRARY_SKILL_ID.test(id));
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.334 if (unknown) return bad(`There is no skill called ${unknown.slice(0, 40)}.`);
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.335 if (library.length > 200) return bad("At most 200 library skills can be off for one agent.");
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.336 // In the skills' own order, so the same choice always reads the same.
Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.337 next.skills_off = [...FOUNDATIONAL_SKILL_IDS.filter((id) => ids.has(id)), ...library.sort()];
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.338 }
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)339 if (changes.faces !== undefined) {
340 if (changes.faces === "customers") return bad("Customer-facing agents aren't available yet.");
341 if (changes.faces !== "internal") return bad("An agent faces internal: the workspace's own people.");
342 next.faces = "internal";
343 }
344 // Never wider than their agent, whichever of the two changed.
345 next.subagents = next.subagents.map((sub) => ({ ...sub, routing: withinParent(sub.routing, next.routing) }));
Chat and workspace agents: channels, DMs and named agents you talk to346 return { ok: true, value: next };
347}
348
349/** A stored JSON column, read defensively: a bad value is the default. */
350export function readJson<T extends object>(raw: unknown, fallback: T): T {
351 if (typeof raw !== "string") return fallback;
352 try {
353 const parsed = JSON.parse(raw) as unknown;
354 return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...fallback, ...(parsed as T) } : fallback;
355 } catch {
356 return fallback;
357 }
358}

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