Skip to content
1,117 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.

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)1/**
2 * What an agent can read while it replies: code, issues, pull requests,
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.3 * chat, the roster, and its colleagues (docs.g1t.sh/guides/agent-access/,
4 * "What an agent can and can't know").
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)5 *
6 * Every tool goes through the reply's `Audience` before it reads
7 * anything, and the check is here, in code:
8 * - code tools are offered only when the audience may read code at all,
9 * and a repository is used only when it is on the audience's allow-list;
10 * - chat tools ask the chat service, which works out the audience from the
11 * conversation itself;
12 * - what the audience may not see comes back as one neutral line,
13 * `WITHHELD`, the same for a thing that is private and a thing that does
14 * not exist, and never names it.
15 *
16 * Whatever a tool returns is wrapped as untrusted data: text in files,
17 * issues and messages is never an instruction to the agent.
18 *
19 * Pure apart from its ports, so the rules are tested adversarially.
20 */
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.21import type { DocEditTarget, FolioAgentEdit, FolioAgentEditResult, FolioAgentRead, FolioAudience, FolioKind, FolioPassage, FolioRef, User } from "@g1t/contracts";
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)22
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.23import { FOLIO_KINDS, folioIdFrom, isFolioKind } from "../../../packages/contracts/src/folios.ts";
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.24import type { MakeFileFormat } from "../../../packages/contracts/src/skills.ts";
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)25import { type Audience, type RepoRef, WITHHELD } from "./audience.ts";
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.26import { makeFile, previewTable, readSheets, sizeLabel } from "./files.ts";
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
28/** One tool, as the Messages API takes it. */
29export type ToolDef = { name: string; description: string; input_schema: Record<string, unknown> };
30
31export type FoundMessage = { channel: string | null; channel_id: string; id: string; author: string; body: string; created_at: string };
32
33/** What the tools reach outside this module. */
34export interface ToolPorts {
35 readFile(repo: RepoRef, viewer: User, ref: string, path: string): Promise<{ text: string | null; size: number } | null>;
36 searchCode(viewer: User, query: string, repo: RepoRef | null): Promise<{ repo: string; path: string; snippet: string }[]>;
37 listIssues(repo: RepoRef, viewer: User, state: "open" | "closed"): Promise<{ number: number; title: string; state: string; labels: string[] }[] | null>;
38 getIssue(repo: RepoRef, number: number, viewer: User): Promise<{ number: number; title: string; state: string; body: string; comments: { author: string; body: string }[] } | null>;
39 getPull(repo: RepoRef, number: number, viewer: User): Promise<{ number: number; title: string; status: string; body: string; checks: string | null } | null>;
40 recentPulls(repos: RepoRef[], viewer: User): Promise<{ repo: string; number: number; title: string; status: string; updated_at: string }[]>;
41 /** Chat's own audience rule applies; null when the search failed. */
42 searchMessages(query: string): Promise<FoundMessage[] | null>;
43 /** Null when the audience may not read it (or it does not exist). */
44 readThread(channelId: string, id: string): Promise<FoundMessage[] | null>;
45 roster(viewer: User | null): Promise<string>;
46 consult(handle: string, question: string): Promise<{ ok: true; colleague: string; answer: string } | { ok: false; message: string }>;
Docs: a workspace knowledge base people and agents write together47 /**
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.48 * The workspace's artifacts (Artifacts mode), as the artifacts service lets
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.49 * this agent use them for the person it acts for and everyone who will
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.50 * read the answer. Absent where there is no artifacts service.
Docs: a workspace knowledge base people and agents write together51 */
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.52 folios?: FoliosPorts;
Docs: a workspace knowledge base people and agents write together53}
54
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.55/** A space as an agent sees it, with what it may do there for the person it acts for. */
56export type FolioSpaceLine = {
57 id: string;
58 slug: string;
59 name: string;
60 description: string | null;
61 kind: string;
62 projects: string[];
63 can: { read: boolean; suggest: boolean; edit: boolean };
64};
65
66/** Where a new artifact goes: a space, its asker's Private, or Private shared with the conversation's people. */
67export type FolioWhere = { space_id: string } | "private" | { conversation: string[] };
68
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.69/** A call's answer: the value, or the artifacts service's error code and sentence. */
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.70export type FolioDone<T> = { ok: true; value: T } | { ok: false; code: string; message: string };
71
72/**
73 * Artifacts, as an agent uses them. Every call names the person it acts
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.74 * for, and the reads also who reads the answer; the artifacts service checks
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.75 * both.
76 */
77export interface FoliosPorts {
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.78 /** Spaces everyone here can read; null when the artifacts service couldn't answer. */
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.79 spaces(viewer: User, audience: FolioAudience): Promise<FolioSpaceLine[] | null>;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.80 /** Passages closest in meaning to `query`; `spaces` (required reading) first. Null when the artifacts service couldn't answer. */
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.81 recall(viewer: User, audience: FolioAudience, query: string, spaces: string[], kinds?: FolioKind[]): Promise<FolioPassage[] | null>;
82 /** Artifacts matching `query` (words and meaning), as lines with links. */
83 search(viewer: User, audience: FolioAudience, input: { query: string; kind: FolioKind | null; space_id: string | null; project: string | null }): Promise<string | null>;
84 read(viewer: User, audience: FolioAudience, folioId: string): Promise<FolioDone<FolioAgentRead>>;
85 /** Artifacts possibly out of date since code they cite changed. */
86 stale(viewer: User, audience: FolioAudience, repo: string | null): Promise<string | null>;
87 create(
88 viewer: User,
89 input: { kind: FolioKind; title: string; markdown: string | null; template_id: string | null; where: FolioWhere; parent_id: string | null; source: { title: string; href: string } | null },
90 ): Promise<FolioDone<FolioRef>>;
91 edit(viewer: User, folioId: string, edit: FolioAgentEdit): Promise<FolioDone<FolioAgentEditResult>>;
92 /** `view` or `comment` for people already in this conversation. */
93 share(viewer: User, audience: FolioAudience, folioId: string, userIds: string[], role: "view" | "comment"): Promise<FolioDone<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.94 /**
95 * Keeps a file the agent made with a doc the asker can edit; `url` is
96 * where it is served (on the usercontent origin). Absent where files
97 * can't be kept.
98 */
99 attach?(viewer: User, folioId: string, file: { name: string; content_type: string; bytes: Uint8Array }): Promise<FolioDone<{ url: string; name: string; bytes: number }>>;
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.100 /** Sends the asker a link directly, as a message from the agent in their DM with it; false when it couldn't. */
101 sendLink(asker: User, link: { title: string; path: string }, note: string): Promise<boolean>;
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}
103
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 asked104/**
105 * What an agent may do, beyond reading: remember, file an issue for the
106 * person who asked, and start or shape work. Each is checked here before it
107 * runs (the audience, the asker, the hop limit) and again by the service
108 * that does it.
109 */
110export interface ActionPorts {
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.111 /**
112 * `onlyForAsker`: this turn read an artifact the whole workspace can't
113 * read, so the fact is kept for the person who asked alone.
114 */
115 remember(body: string, scope: "workspace" | "channel" | "person" | null, onlyForAsker?: boolean): Promise<{ ok: boolean; message: string }>;
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 asked116 forget(id: string): Promise<{ ok: boolean; message: string }>;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar117 /**
118 * Posts a draft issue as a card in the conversation, with File issue and
119 * Discard: whoever presses File files it as themselves, if they can read
120 * the repository. Nothing is filed by the agent.
121 */
122 draftIssue(repo: RepoRef, input: { title: string; body: string; labels: string[] }): Promise<{ ok: boolean; message: string }>;
123 /**
124 * Comments on an issue or pull request, or reviews a pull request, as the
125 * agent on behalf of the person who asked. Reviews are advisory: they
126 * never count toward required approvals.
127 */
128 comment?(repo: RepoRef, asker: User, number: number, body: string): Promise<{ ok: boolean; message: string }>;
129 review?(repo: RepoRef, asker: User, number: number, verdict: "comment" | "approve" | "request_changes", body: string): Promise<{ ok: boolean; message: string }>;
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 /** From chat: spins off a session for real work. */
131 startSession?(title: string, goal: string): Promise<{ ok: boolean; message: string }>;
132 /** In a session: a short progress note in its thread. */
133 postUpdate?(text: string): Promise<{ ok: boolean; message: string }>;
134 /** In a session: one of the agent's own subagents takes part of the work. */
135 useSubagent?(name: string, brief: string): Promise<{ ok: boolean; message: string }>;
136 /** In a session: a colleague works on part of it, paid from this session's budget. */
137 bringIn?(handle: string, brief: string): Promise<{ ok: boolean; message: string }>;
Merge branch 'worktree-agent-a1398e81ad1a64c5f'138 /** From chat: a colleague takes the work on for the person who asked, here or in a group message. */
139 handOff?(handle: string, brief: string): Promise<{ ok: boolean; message: string }>;
Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked140}
141
Merge branch 'worktree-agent-a1398e81ad1a64c5f'142/** The most hand-offs one reply makes. */
143export const MAX_HAND_OFFS = 2;
144
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)145/** The most tool calls one reply makes. */
146export const MAX_TOOL_CALLS = 8;
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/** The most tool calls one step of a session makes. */
148export const MAX_SESSION_TOOL_CALLS = 24;
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)149/** The most of a file or result an answer is given, in characters. */
150const MAX_RESULT = 20_000;
151
152export type ToolCall = {
153 tool: string;
154 /** Its arguments, with long text cut, as recorded. */
155 args: string;
156 outcome: "allowed" | "withheld" | "refused" | "error";
157 bytes: number;
158};
159
160export type ToolResult = { text: string; outcome: ToolCall["outcome"] };
161
162/**
163 * Text a tool read, marked as data. Anything in it that looks like the
164 * closing mark is defused, so content can't end the block early.
165 */
166export function untrusted(source: string, content: string): string {
167 const safe = (text: string) => text.replace(/<\/?untrusted/gi, (mark) => mark.replace("<", "&lt;"));
168 const body = content.length > MAX_RESULT ? `${content.slice(0, MAX_RESULT)}\n[cut: ${content.length - MAX_RESULT} more characters]` : content;
169 return `<untrusted source="${safe(source).replace(/"/g, "'")}">\n${safe(body)}\n</untrusted>`;
170}
171
172/** Arguments as recorded: every string cut to 120 characters. */
173export function redact(args: unknown): string {
174 const cut = (value: unknown): unknown => {
175 if (typeof value === "string") return value.length > 120 ? `${value.slice(0, 120)}…` : value;
176 if (Array.isArray(value)) return value.slice(0, 10).map(cut);
177 if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).slice(0, 10).map(([k, v]) => [k, cut(v)]));
178 return value;
179 };
180 return JSON.stringify(cut(args ?? {})).slice(0, 1000);
181}
182
183const CODE_TOOLS: ToolDef[] = [
184 {
185 name: "list_repositories",
186 description: "The workspace's repositories everyone in this conversation can read. Start here to know what you can look at.",
187 input_schema: { type: "object", properties: {} },
188 },
189 {
190 name: "search_code",
191 description: "Search code on default branches. Optionally only in one repository (`name` or `workspace/name`).",
192 input_schema: { type: "object", properties: { query: { type: "string" }, repo: { type: "string" } }, required: ["query"] },
193 },
194 {
195 name: "read_file",
196 description: "Read a file from a repository, at its default branch or a ref.",
197 input_schema: { type: "object", properties: { repo: { type: "string" }, path: { type: "string" }, ref: { type: "string" } }, required: ["repo", "path"] },
198 },
199 {
200 name: "list_issues",
201 description: "A repository's newest issues, open by default.",
202 input_schema: { type: "object", properties: { repo: { type: "string" }, state: { type: "string", enum: ["open", "closed"] } }, required: ["repo"] },
203 },
204 {
205 name: "get_issue",
206 description: "One issue with its comments.",
207 input_schema: { type: "object", properties: { repo: { type: "string" }, number: { type: "integer" } }, required: ["repo", "number"] },
208 },
209 {
210 name: "get_pull",
211 description: "One pull request: what it changes, its status and checks.",
212 input_schema: { type: "object", properties: { repo: { type: "string" }, number: { type: "integer" } }, required: ["repo", "number"] },
213 },
214 {
215 name: "recent_activity",
216 description: "Recently merged and open pull requests, in one repository or across those you can read.",
217 input_schema: { type: "object", properties: { repo: { type: "string" } } },
218 },
219];
220
221const CHAT_TOOLS: ToolDef[] = [
222 {
223 name: "search_messages",
224 description: "Search chat messages this conversation's people can all read.",
225 input_schema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
226 },
227 {
228 name: "read_thread",
229 description: "Read a chat thread by its channel id and a message id in it (from search_messages).",
230 input_schema: { type: "object", properties: { channel: { type: "string" }, id: { type: "string" } }, required: ["channel", "id"] },
231 },
232 {
233 name: "workspace_roster",
234 description: "The workspace's people and agents: names, teams, titles and roles.",
235 input_schema: { type: "object", properties: {} },
236 },
237];
238
239const ASK_COLLEAGUE: ToolDef = {
240 name: "ask_colleague",
241 description:
Merge branch 'worktree-agent-a1398e81ad1a64c5f'242 "Ask a colleague agent a quick question, privately: their answer comes back to you alone, they do no work in the conversation, and the work stays yours. Use it when their role knows something yours doesn't. To give them the work itself, use hand_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)243 input_schema: { type: "object", properties: { handle: { type: "string" }, question: { type: "string" } }, required: ["handle", "question"] },
244};
245
Merge branch 'worktree-agent-a1398e81ad1a64c5f'246const HAND_OFF: ToolDef = {
247 name: "hand_off",
248 description:
249 "Hand work to a colleague agent, for the person who asked: they take it on and answer that person themselves, with that person's access. If they are a member of this conversation (a channel or group message), your brief is posted here; otherwise a group message opens (or is reused) with the person who asked, you and them, your brief goes there, and a card here links to it. It is the only way to get a colleague working: an @mention in your message wakes nobody. Give their handle and a complete brief written to them: what is wanted, why, what done looks like, and what they need from this conversation. For a quick question you answer with, use ask_colleague instead.",
250 input_schema: { type: "object", properties: { handle: { type: "string" }, brief: { type: "string" } }, required: ["handle", "brief"] },
251};
252
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 asked253const REMEMBER: ToolDef = {
254 name: "remember",
255 description:
256 "Keep a short fact for later work: a preference, a decision, who owns what, how something works here. One fact per call, in your own words. It is kept where this conversation allows (this person, this conversation, or the workspace from a public channel), with this conversation as its source. Never keep secrets, credentials or customers' personal data.",
257 input_schema: {
258 type: "object",
259 properties: { fact: { type: "string" }, scope: { type: "string", enum: ["workspace", "channel", "person"] } },
260 required: ["fact"],
261 },
262};
263
264const FORGET: ToolDef = {
265 name: "forget",
266 description: "Forget one of the notes under 'What you remember', by its id, when it is wrong or out of date.",
267 input_schema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
268};
269
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar270const DRAFT_ISSUE: ToolDef = {
271 name: "draft_issue",
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 asked272 description:
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar273 "Draft an issue (a bug report or a feature request) for a repository with what you found. It appears in the conversation as a card with File issue and Discard buttons: the person files it themselves with one press, so don't ask them to confirm in words. Write it for the team that will fix it: what happens, what should happen, steps or evidence, and where in the code it likely is.",
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 asked274 input_schema: {
275 type: "object",
276 properties: {
277 repo: { type: "string" },
278 title: { type: "string" },
279 body: { type: "string" },
280 labels: { type: "array", items: { type: "string" } },
281 },
282 required: ["repo", "title", "body"],
283 },
284};
285
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar286const COMMENT: ToolDef = {
287 name: "comment",
288 description:
289 "Comment on an issue or pull request, as yourself on behalf of the person you're working for. Use it when the comment belongs on the issue or pull request (findings, a test plan, a question for its author), not for chatting.",
290 input_schema: {
291 type: "object",
292 properties: { repo: { type: "string" }, number: { type: "integer" }, body: { type: "string" } },
293 required: ["repo", "number", "body"],
294 },
295};
296
297const REVIEW_PULL: ToolDef = {
298 name: "review_pull",
299 description:
300 "Review a pull request on the pull request itself, as yourself on behalf of the person you're working for: approve, request changes, or just comment, with your review in the body. Your review is advisory: people still give the approvals a merge needs. Read the change first.",
301 input_schema: {
302 type: "object",
303 properties: {
304 repo: { type: "string" },
305 number: { type: "integer" },
306 verdict: { type: "string", enum: ["comment", "approve", "request_changes"] },
307 body: { type: "string" },
308 },
309 required: ["repo", "number", "verdict", "body"],
310 },
311};
312
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 asked313const START_SESSION: ToolDef = {
314 name: "start_session",
315 description:
316 "Spin off a session for work that needs more than a quick answer: investigating, reading a lot of code, writing something long, or anything that takes several steps. It runs on its own with its own context, shows a live card here, and reports back in this conversation when done. Give it a short title and a complete brief: the goal, what done looks like, and everything it needs from this conversation.",
317 input_schema: { type: "object", properties: { title: { type: "string" }, goal: { type: "string" } }, required: ["title", "goal"] },
318};
319
320const POST_UPDATE: ToolDef = {
321 name: "post_update",
322 description: "Post a short progress note in your session's thread, for the people following it. Use it for real milestones or a question, not for every step.",
323 input_schema: { type: "object", properties: { text: { type: "string" } }, required: ["text"] },
324};
325
326const USE_SUBAGENT: ToolDef = {
327 name: "use_subagent",
328 description:
329 "Hand a well-defined part of this session to one of your subagents (listed under Subagents). It works in its own session, paid from this one, and its result comes back to you before you go on. Give a complete brief.",
330 input_schema: { type: "object", properties: { name: { type: "string" }, brief: { type: "string" } }, required: ["name", "brief"] },
331};
332
333const BRING_IN: ToolDef = {
334 name: "bring_in",
335 description:
336 "Bring a colleague in on part of this session when their role owns it. They work in their own session, paid from this one, and their result comes back to you before you go on. Give a complete brief.",
337 input_schema: { type: "object", properties: { handle: { type: "string" }, brief: { type: "string" } }, required: ["handle", "brief"] },
338};
339
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.340/** What every artifact tool's description starts from, so the model never mixes them up with workflow run artifacts. */
341const ARTIFACTS =
342 "Artifacts are the workspace's own documents, made and shared in its Artifacts section: docs now, and later slides, designs and dashboards. They are not a workflow run's build artifacts.";
343
344const FOLIO_TOOLS: ToolDef[] = [
Docs: a workspace knowledge base people and agents write together345 {
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.346 name: "search_artifacts",
347 description: `${ARTIFACTS} Search the artifacts everyone in this conversation can read (specs, runbooks, policies, onboarding, decisions), by words and meaning, plus projects' docs. Optionally only one kind, one space (its name or id from list_spaces) or one project (\`workspace/name\`). Each result has its link and id. Look here first for how things work and what was decided.`,
348 input_schema: {
349 type: "object",
350 properties: {
351 query: { type: "string" },
352 kind: { type: "string", enum: [...FOLIO_KINDS] },
353 space: { type: "string", description: "A space's name or id." },
354 project: { type: "string", description: "A repository, `workspace/name`." },
355 },
356 required: ["query"],
357 },
Docs: a workspace knowledge base people and agents write together358 },
359 {
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.360 name: "read_artifact",
361 description: `${ARTIFACTS} Read one artifact by its id (fol_…) or its link (…/-/artifacts/<name>-fol_…). A doc comes back as Markdown with the ids of its top-level blocks (for edit_artifact), and says what you may do with it.`,
362 input_schema: { type: "object", properties: { id: { type: "string", description: "Its id or link." } }, required: ["id"] },
Docs: a workspace knowledge base people and agents write together363 },
364 {
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.365 name: "list_spaces",
366 description: `${ARTIFACTS} The spaces whose artifacts everyone in this conversation can read, with what you may do in each (read, suggest, edit).`,
367 input_schema: { type: "object", properties: {} },
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store368 },
369 {
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.370 name: "stale_artifacts",
371 description: `${ARTIFACTS} Artifacts possibly out of date because code they cite changed. Optionally only for one repository (\`workspace/name\`). Start here when keeping the docs current: read each, then bring it up to date with edit_artifact and marks_current.`,
372 input_schema: { type: "object", properties: { repo: { type: "string" } } },
Docs: a workspace knowledge base people and agents write together373 },
374];
375
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.376const FOLIO_WRITE_TOOLS: ToolDef[] = [
377 {
378 name: "create_artifact",
379 description: `${ARTIFACTS} Make a new artifact ("write this up"). Only kind "doc" can be made for now. Give a title and its content as Markdown (or a template id). where: a space (its name or id from list_spaces) as { "space": "..." }, "private" for the person who asked alone, or "conversation" for them plus view access for this conversation's people. Left out: shared with this conversation in a direct message or private channel, the General space in a public channel. It belongs to the person who asked, and you can keep editing it. When it comes from a conversation, set source to that thread's link.`,
380 input_schema: {
381 type: "object",
382 properties: {
383 kind: { type: "string", enum: [...FOLIO_KINDS] },
384 title: { type: "string" },
385 content: { type: "string", description: "Markdown." },
386 template: { type: "string", description: "A template id, instead of content." },
387 where: {
388 description: '{ "space": "<name or id>" }, "private" or "conversation".',
389 anyOf: [
390 { type: "string", enum: ["private", "conversation"] },
391 { type: "object", properties: { space: { type: "string" } }, required: ["space"] },
392 ],
393 },
394 parent: { type: "string", description: "A doc to put it under: its id or link." },
395 source: { type: "string", description: "The link of the thread it was written up from." },
396 },
397 required: ["kind", "title"],
398 },
399 },
Docs: a workspace knowledge base people and agents write together400 {
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.401 name: "edit_artifact",
402 description: `${ARTIFACTS} Change a doc: replace a section (by its heading), a range of top-level blocks (ids from read_artifact), the whole doc, or add to the end. Where you may edit, it applies at once and shows in its history as yours; elsewhere it becomes a suggestion people accept or reject inline. Read it first. Write Markdown. Only docs can be changed this way for now.`,
Docs: a workspace knowledge base people and agents write together403 input_schema: {
404 type: "object",
405 properties: {
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.406 id: { type: "string", description: "Its id or link." },
Docs: a workspace knowledge base people and agents write together407 target: { type: "string", enum: ["append", "section", "blocks", "document"] },
408 heading: { type: "string", description: "For target section: the heading's text." },
409 from_block: { type: "string" },
410 to_block: { type: "string" },
411 markdown: { type: "string" },
412 note: { type: "string", description: "Why, in a line, for the history or the suggestion." },
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.413 marks_current: { type: "boolean", description: "This edit brings a doc marked possibly out of date up to date: it clears the mark when it applies or is accepted." },
Docs: a workspace knowledge base people and agents write together414 suggest_only: { type: "boolean", description: "Suggest even where you could edit." },
415 },
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.416 required: ["id", "target", "markdown"],
Docs: a workspace knowledge base people and agents write together417 },
418 },
419 {
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.420 name: "share_artifact",
421 description: `${ARTIFACTS} Let people in this conversation view or comment on an artifact, when the person who asked has full access to it. Only in a direct message or a private channel, and only with people already in it. You can't give edit or full access, or change who else can open it: for that, ask the person to use Share.`,
Docs: a workspace knowledge base people and agents write together422 input_schema: {
423 type: "object",
424 properties: {
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.425 id: { type: "string", description: "Its id or link." },
426 people: { type: "array", items: { type: "string" }, description: "Usernames of people in this conversation." },
427 role: { type: "string", enum: ["view", "comment"] },
Docs: a workspace knowledge base people and agents write together428 },
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.429 required: ["id", "people", "role"],
Docs: a workspace knowledge base people and agents write together430 },
431 },
432];
433
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.434/**
435 * Files people asked for (the Documents, Data and Files and media skills):
436 * written here, kept with a doc the person who asked owns or can edit, and
437 * served from the usercontent origin like an upload.
438 */
439const MAKE_FILE: ToolDef = {
440 name: "make_file",
441 description:
442 'Make a file someone asked for and keep it with a doc in Artifacts: format "pdf" or "docx" (a Word document) from Markdown in content (headings, paragraphs, bold, italic, code, links, lists, quotes, code blocks, tables); "xlsx" (a spreadsheet, one or more sheets) or "csv" (one sheet) from sheets, each { name, rows }, the first row its header and numbers as numbers; or "md". Give a title (the file is named after it). Without artifact, a new doc is made holding the content (or a preview of the rows) with the file attached; with artifact (a doc\'s id or link you can edit), the file is attached to that doc. where works as for create_artifact. It returns the file\'s link: give it to them.',
443 input_schema: {
444 type: "object",
445 properties: {
446 format: { type: "string", enum: ["pdf", "docx", "xlsx", "csv", "md"] },
447 title: { type: "string" },
448 content: { type: "string", description: "Markdown, for pdf, docx and md." },
449 sheets: {
450 type: "array",
451 description: "For xlsx and csv.",
452 items: {
453 type: "object",
454 properties: { name: { type: "string" }, rows: { type: "array", items: { type: "array", items: {} } } },
455 required: ["rows"],
456 },
457 },
458 artifact: { type: "string", description: "A doc to attach it to: its id or link. Left out: a new doc." },
459 where: {
460 description: '{ "space": "<name or id>" }, "private" or "conversation", for a new doc.',
461 anyOf: [{ type: "string" }, { type: "object", properties: { space: { type: "string" } }, required: ["space"] }],
462 },
463 },
464 required: ["format", "title"],
465 },
466};
467
468const FOLIO_NAMES = new Set([...FOLIO_TOOLS, ...FOLIO_WRITE_TOOLS, MAKE_FILE].map((tool) => tool.name));
Docs: a workspace knowledge base people and agents write together469
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.470/** Every tool an agent may be offered, by name: what skills may name (@g1t/contracts skills.ts). */
471export const TOOL_NAMES: ReadonlySet<string> = new Set(
472 [...CODE_TOOLS, ...CHAT_TOOLS, ...FOLIO_TOOLS, ...FOLIO_WRITE_TOOLS, MAKE_FILE, ASK_COLLEAGUE, HAND_OFF, REMEMBER, FORGET, DRAFT_ISSUE, COMMENT, REVIEW_PULL, START_SESSION, POST_UPDATE, USE_SUBAGENT, BRING_IN].map((tool) => tool.name),
473);
474
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)475const CODE_NAMES = new Set(CODE_TOOLS.map((tool) => tool.name));
476
477export type ToolContext = {
478 /** The agent replying. */
479 agentId: string;
480 /** Handles nobody may consult from here: the agent itself, and whoever sent it the work. */
481 notConsult: string[];
482 /** Hops so far: a consult is one more, and none is offered at the limit. */
483 hops: number;
484 maxHops: number;
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 asked485 /** Whether this is a session's step (more calls, session tools) or a reply. */
486 session?: boolean;
487 /** Told of every call as it is made, for a session's transcript. */
488 onCall?: (call: ToolCall) => void;
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)489};
490
491export class ToolBox {
492 /** Every call this reply made, shared with the tool boxes of colleagues it consults: one budget for the reply. */
493 readonly calls: ToolCall[];
494 private readonly audience: Audience;
495 private readonly ports: ToolPorts;
496 private readonly context: ToolContext;
497
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 asked498 private readonly actions: ActionPorts | null;
499 /** Updates posted in this step. */
500 private updates = 0;
Merge branch 'worktree-agent-a1398e81ad1a64c5f'501 /** Hand-offs made in this reply. */
502 private handOffs = 0;
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.503 /** Artifacts read or made in this turn that not everyone here can read: never named here. */
504 private readonly notHere = new Set<string>();
505 /**
506 * Whether this turn read an artifact the whole workspace can't: then what
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.507 * it remembers is kept for the person who asked alone.
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.508 */
509 private privateRead = false;
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 asked510
511 constructor(audience: Audience, ports: ToolPorts, context: ToolContext, calls: ToolCall[] = [], actions: ActionPorts | null = 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)512 this.audience = audience;
513 this.ports = ports;
514 this.context = context;
515 this.calls = calls;
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 asked516 this.actions = actions;
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)517 }
518
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 asked519 /** A colleague's tool box for a consult: the same audience, the same budget, one hop further, reading only. */
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)520 forColleague(ports: ToolPorts, context: ToolContext): ToolBox {
521 return new ToolBox(this.audience, ports, context, this.calls);
522 }
523
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 asked524 /** The most calls this box makes. */
525 get maxCalls(): number {
526 return this.context.session ? MAX_SESSION_TOOL_CALLS : MAX_TOOL_CALLS;
527 }
528
529 /** Whether the person who asked can be acted for: resolved, and able to read code here. */
530 private canFile(): boolean {
531 return !!this.actions && !!this.audience.asker && this.audience.codeAllowed();
532 }
533
534 /**
535 * The tools offered: no code tools for an audience that can't read code,
536 * no consults or hand-offs at the hop limit, session tools only in a
537 * session, and a spin-off only from chat.
538 */
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)539 definitions(): ToolDef[] {
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 asked540 const roomForHop = this.context.hops + 1 <= this.context.maxHops;
541 const actions = this.actions;
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)542 return [
543 ...(this.audience.codeAllowed() ? CODE_TOOLS : []),
544 ...CHAT_TOOLS,
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.545 // Artifacts are for everyone, Code or not: the artifacts service decides what this person and audience can read.
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.546 ...(this.ports.folios && this.audience.asker ? FOLIO_TOOLS : []),
547 ...(this.ports.folios && this.audience.asker && actions ? FOLIO_WRITE_TOOLS : []),
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.548 ...(this.ports.folios?.attach && this.audience.asker && actions ? [MAKE_FILE] : []),
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 asked549 ...(roomForHop ? [ASK_COLLEAGUE] : []),
550 ...(actions ? [REMEMBER, FORGET] : []),
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar551 ...(this.canFile() ? [DRAFT_ISSUE] : []),
552 ...(this.canFile() && actions?.comment ? [COMMENT] : []),
553 ...(this.canFile() && actions?.review ? [REVIEW_PULL] : []),
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 asked554 ...(actions?.startSession && !this.context.session ? [START_SESSION] : []),
Merge branch 'worktree-agent-a1398e81ad1a64c5f'555 ...(actions?.handOff && !this.context.session && roomForHop ? [HAND_OFF] : []),
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 asked556 ...(actions?.postUpdate && this.context.session ? [POST_UPDATE] : []),
557 ...(actions?.useSubagent && this.context.session && roomForHop ? [USE_SUBAGENT] : []),
558 ...(actions?.bringIn && this.context.session && roomForHop ? [BRING_IN] : []),
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)559 ];
560 }
561
562 /** Whether another call may be made. */
563 get spent(): boolean {
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 asked564 return this.calls.length >= this.maxCalls;
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)565 }
566
567 async run(name: string, input: Record<string, unknown>): Promise<ToolResult> {
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 asked568 const result = await this.attempt(name, input);
569 const call: ToolCall = { tool: name, args: redact(input), outcome: result.outcome, bytes: result.text.length };
570 this.calls.push(call);
571 this.context.onCall?.(call);
572 return result;
573 }
574
575 private async attempt(name: string, input: Record<string, unknown>): Promise<ToolResult> {
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)576 let result: ToolResult;
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 asked577 const what = this.context.session ? "step" : "reply";
578 if (this.spent) result = { text: `No more tool calls in this ${what} (at most ${this.maxCalls}). Answer with what you have.`, outcome: "refused" };
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)579 else {
580 try {
581 result = await this.dispatch(name, input ?? {});
582 } catch (error) {
583 console.error("agents: a tool failed", name, String(error));
584 result = { text: "That didn't work just now. Answer with what you have.", outcome: "error" };
585 }
586 }
587 return result;
588 }
589
590 private withheld(): ToolResult {
591 return { text: WITHHELD, outcome: "withheld" };
592 }
593
594 private async dispatch(name: string, input: Record<string, unknown>): Promise<ToolResult> {
595 const asker = this.audience.asker;
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.596 if (FOLIO_NAMES.has(name)) {
597 if (!this.definitions().some((tool) => tool.name === name) || !asker || !this.ports.folios) return { text: `There is no tool called ${name} here.`, outcome: "refused" };
598 return this.folios(name, input, asker, this.ports.folios);
Docs: a workspace knowledge base people and agents write together599 }
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)600 if (CODE_NAMES.has(name)) {
601 // Not offered, and refused if asked for anyway: the check is here, not in the prompt.
602 if (!this.audience.codeAllowed() || !asker) return this.withheld();
603 return this.code(name, input, asker);
604 }
605 switch (name) {
606 case "search_messages": {
607 const query = String(input.query ?? "").trim();
608 if (query.length < 2) return { text: "Search for at least two characters.", outcome: "refused" };
609 const found = await this.ports.searchMessages(query);
610 if (found === null) return { text: "Search didn't work just now.", outcome: "error" };
611 if (!found.length) return { text: "No messages found.", outcome: "allowed" };
612 return { text: untrusted(`search_messages "${query}"`, found.map(messageLine).join("\n")), outcome: "allowed" };
613 }
614 case "read_thread": {
615 const thread = await this.ports.readThread(String(input.channel ?? ""), String(input.id ?? ""));
616 if (!thread || !thread.length) return this.withheld();
617 return { text: untrusted("read_thread", thread.map(messageLine).join("\n")), outcome: "allowed" };
618 }
619 case "workspace_roster":
620 return { text: untrusted("workspace_roster", await this.ports.roster(asker)), outcome: "allowed" };
621 case "ask_colleague": {
622 const handle = String(input.handle ?? "").trim().replace(/^@/, "").toLowerCase();
623 const question = String(input.question ?? "").trim();
624 if (!handle || !question) return { text: "Name the colleague and the question.", outcome: "refused" };
625 if (this.context.hops + 1 > this.context.maxHops) return { text: "This request has been passed along too many times; answer with what you have.", outcome: "refused" };
626 if (this.context.notConsult.includes(handle)) {
627 return { text: `You can't consult @${handle} here: they sent you this work, or it is you. Answer with what you have.`, outcome: "refused" };
628 }
629 const answer = await this.ports.consult(handle, question.slice(0, 2000));
630 if (!answer.ok) return { text: answer.message, outcome: "refused" };
631 return { text: untrusted(`@${answer.colleague}'s answer`, answer.answer), outcome: "allowed" };
632 }
633 default:
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 asked634 return this.act(name, input);
635 }
636 }
637
Agents recall what Docs say before they answer or work, and each has required reading638 /**
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.639 * What the workspace's artifacts say about `query`, for this person and
640 * this audience, before the agent answers: no tool call, nothing counted
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.641 * against its tools. Empty when there is no artifacts service or nothing
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.642 * relevant.
Agents recall what Docs say before they answer or work, and each has required reading643 */
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.644 async recall(query: string | null, spaces: string[]): Promise<FolioPassage[]> {
645 const folios = this.ports.folios;
Agents recall what Docs say before they answer or work, and each has required reading646 const asker = this.audience.asker;
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.647 if (!folios || !asker || !query) return [];
Agents recall what Docs say before they answer or work, and each has required reading648 try {
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.649 return (await folios.recall(asker, this.folioAudience(), query, spaces)) ?? [];
Agents recall what Docs say before they answer or work, and each has required reading650 } catch (error) {
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.651 console.error("agents: artifacts recall failed", String(error));
Agents recall what Docs say before they answer or work, and each has required reading652 return [];
653 }
654 }
655
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.656 /** Who reads what an agent says here, as the artifacts service takes it. */
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.657 private folioAudience(): FolioAudience {
Docs: a workspace knowledge base people and agents write together658 return this.audience.shared ? { kind: "workspace" } : { kind: "people", user_ids: this.audience.members.map((m) => m.id) };
659 }
660
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.661 /** Whether anyone besides the person who asked reads what is said here. */
662 private othersHere(asker: User): boolean {
663 return this.audience.shared || this.audience.members.some((m) => m.id !== asker.id);
664 }
665
666 private async folios(name: string, input: Record<string, unknown>, asker: User, folios: FoliosPorts): Promise<ToolResult> {
Docs: a workspace knowledge base people and agents write together667 const text = (key: string, max: number) => String(input[key] ?? "").trim().slice(0, max);
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.668 const audience = this.folioAudience();
Docs: a workspace knowledge base people and agents write together669 switch (name) {
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.670 case "list_spaces": {
671 const spaces = await folios.spaces(asker, audience);
672 if (spaces === null) return { text: "Spaces couldn't be listed just now.", outcome: "error" };
673 if (!spaces.length) return { text: "There are no spaces everyone here can read.", outcome: "allowed" };
674 return { text: untrusted("list_spaces", spaces.map(spaceLine).join("\n")), outcome: "allowed" };
675 }
676 case "search_artifacts": {
Docs: a workspace knowledge base people and agents write together677 const query = text("query", 200);
678 if (query.length < 2) return { text: "Search for at least two characters.", outcome: "refused" };
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.679 const kind = text("kind", 20) || null;
680 if (kind && !isFolioKind(kind)) return { text: `There is no kind of artifact called ${kind}: it is doc, slides, design or dashboard.`, outcome: "refused" };
681 let spaceId: string | null = null;
682 if (text("space", 200)) {
683 const space = findSpace((await folios.spaces(asker, audience)) ?? [], text("space", 200));
684 if (!space) return this.withheld();
685 spaceId = space.id;
686 }
687 const found = await folios.search(asker, audience, { query, kind: kind as FolioKind | null, space_id: spaceId, project: text("project", 200).toLowerCase() || null });
688 if (found === null) return { text: "Search didn't work just now.", outcome: "error" };
689 return { text: untrusted(`search_artifacts "${query}"`, found), outcome: "allowed" };
690 }
691 case "stale_artifacts": {
692 const found = await folios.stale(asker, audience, text("repo", 200).toLowerCase() || null);
693 if (found === null) return { text: "Out-of-date artifacts couldn't be listed just now.", outcome: "error" };
694 return { text: untrusted("stale_artifacts", found), outcome: "allowed" };
Docs: a workspace knowledge base people and agents write together695 }
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.696 case "read_artifact": {
697 const id = folioRef(text("id", 500));
698 if (!id) return { text: "Give the artifact's id (fol_…) or its link.", outcome: "refused" };
699 const found = await folios.read(asker, audience, id);
700 if (!found.ok) return found.code === "not_found" || found.code === "forbidden" ? this.withheld() : { text: found.message, outcome: "refused" };
701 const read = found.value;
702 if (!this.audience.shared || !read.audience_can_read) this.privateRead = true;
703 if (!read.audience_can_read) return this.notForEveryone(asker, read.folio, folios, "found");
704 return { text: untrusted(`read_artifact ${id}`, folioReadText(read)), outcome: "allowed" };
Docs: a workspace knowledge base people and agents write together705 }
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.706 case "create_artifact": {
707 const kind = text("kind", 20) || "doc";
708 if (!isFolioKind(kind)) return { text: `There is no kind of artifact called ${kind}.`, outcome: "refused" };
709 if (kind !== "doc") return { text: NOT_YET, outcome: "refused" };
710 const title = text("title", 200);
711 const template = text("template", 100) || null;
712 const markdown = String(input.content ?? "").slice(0, 100_000);
713 if (!title) return { text: "An artifact needs a title.", outcome: "refused" };
714 if (!markdown.trim() && !template) return { text: "Give its content as Markdown, or a template.", outcome: "refused" };
715 const parentGiven = text("parent", 500);
716 const parent = parentGiven ? folioRef(parentGiven) : null;
717 if (parentGiven && !parent) return { text: "Give the parent doc's id or link.", outcome: "refused" };
718 const place = await this.whereFor(input.where, asker, folios);
719 if (!place.ok) return { text: place.message, outcome: "refused" };
720 const make = (where: FolioWhere) =>
721 folios.create(asker, { kind, title, markdown: template ? null : markdown, template_id: template, where, parent_id: parent, source: sourceLink(text("source", 2000)) });
722 let made = await make(place.where);
723 // The General space by default, unless the person who asked can't add there: then their Private.
724 if (!made.ok && place.fallback && made.code === "forbidden") made = await make("private");
725 if (!made.ok) return { text: `It couldn't be made: ${made.message}`, outcome: "refused" };
726 const ref = made.value;
727 if (this.othersHere(asker)) {
728 const check = await folios.read(asker, audience, ref.id).catch(() => null);
729 if (!check?.ok || !check.value.audience_can_read) return this.notForEveryone(asker, ref, folios, "made");
730 }
731 return { text: `Wrote ${ref.title} (${ref.path}, id ${ref.id}). Link it.`, outcome: "allowed" };
732 }
733 case "edit_artifact": {
734 const id = folioRef(text("id", 500));
Docs: a workspace knowledge base people and agents write together735 const markdown = String(input.markdown ?? "").slice(0, 100_000);
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.736 if (!id || !markdown.trim()) return { text: "Give the artifact's id or link, and the Markdown.", outcome: "refused" };
Docs: a workspace knowledge base people and agents write together737 const kind = text("target", 20);
738 const target: DocEditTarget | null =
739 kind === "append"
740 ? { kind: "append" }
741 : kind === "document"
742 ? { kind: "document" }
743 : kind === "section" && text("heading", 300)
744 ? { kind: "section", heading: text("heading", 300) }
745 : kind === "blocks" && text("from_block", 100) && text("to_block", 100)
746 ? { kind: "blocks", from_block: text("from_block", 100), to_block: text("to_block", 100) }
747 : null;
748 if (!target) return { text: "Say what to change: append, a section by its heading, blocks by their ids, or the whole document.", outcome: "refused" };
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.749 const edit: FolioAgentEdit = { kind: "doc", target, markdown, note: text("note", 300) || null, suggest_only: input.suggest_only === true, marks_current: input.marks_current === true };
750 const done = await folios.edit(asker, id, edit);
751 if (!done.ok) return { text: `That didn't work: ${done.message}`, outcome: "refused" };
752 return { text: editMessage(done.value, !this.notHere.has(done.value.folio.id)), outcome: "allowed" };
Docs: a workspace knowledge base people and agents write together753 }
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.754 case "share_artifact": {
755 if (this.audience.shared) return { text: "You can share only in a direct message or a private channel. Ask the person to use Share on the artifact instead.", outcome: "refused" };
756 const id = folioRef(text("id", 500));
757 if (!id) return { text: "Give the artifact's id (fol_…) or its link.", outcome: "refused" };
758 const role = input.role === "view" || input.role === "comment" ? input.role : null;
759 if (!role) return { text: "You can share to view or comment only. For more, ask the person to use Share.", outcome: "refused" };
760 const named = (Array.isArray(input.people) ? input.people : []).filter((p): p is string => typeof p === "string").map((p) => p.trim().replace(/^@/, "").toLowerCase()).filter(Boolean).slice(0, 50);
761 const people = named.map((n) => this.audience.members.find((m) => m.username.toLowerCase() === n || m.id === n) ?? n);
762 const outside = people.filter((p): p is string => typeof p === "string");
763 if (outside.length) return { text: `${outside.map((n) => `@${n}`).join(", ")} ${outside.length === 1 ? "isn't" : "aren't"} in this conversation: you can share only with people in it.`, outcome: "refused" };
764 const users = [...new Map((people as User[]).filter((u) => u.id !== asker.id).map((u) => [u.id, u])).values()];
765 if (!users.length) return { text: "Name who to share it with: people in this conversation besides the person who asked.", outcome: "refused" };
766 const done = await folios.share(asker, audience, id, users.map((u) => u.id), role);
767 if (!done.ok) return { text: `It couldn't be shared: ${done.message}`, outcome: "refused" };
768 return { text: `Shared with ${users.map((u) => `@${u.username}`).join(", ")}: they can ${role} it.`, outcome: "allowed" };
Docs: a workspace knowledge base people and agents write together769 }
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.770 case "make_file":
771 return this.makeFile(input, asker, folios);
Docs: a workspace knowledge base people and agents write together772 default:
773 return { text: `There is no tool called ${name}.`, outcome: "refused" };
774 }
775 }
776
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.777 /**
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.778 * A file someone asked for (files.ts), kept with a doc: the one named,
779 * which the asker must be able to edit, or a new one holding the content.
780 * Where not everyone here can read that doc, the link goes to the asker
781 * directly, as for any artifact.
782 */
783 private async makeFile(input: Record<string, unknown>, asker: User, folios: FoliosPorts): Promise<ToolResult> {
784 if (!folios.attach) return { text: "There is no tool called make_file here.", outcome: "refused" };
785 const title = String(input.title ?? "").trim().slice(0, 200);
786 const made = makeFile({ format: input.format, title, content: input.content, sheets: input.sheets });
787 if (!made.ok) return { text: made.message, outcome: "refused" };
788 const file = made.file;
789 const audience = this.folioAudience();
790 const given = String(input.artifact ?? "").trim().slice(0, 500);
791 let ref: FolioRef;
792 let hidden = false;
793 if (given) {
794 const id = folioRef(given);
795 if (!id) return { text: "Give the doc's id (fol_…) or its link, or leave artifact out for a new doc.", outcome: "refused" };
796 const found = await folios.read(asker, audience, id);
797 if (!found.ok) return found.code === "not_found" || found.code === "forbidden" ? this.withheld() : { text: found.message, outcome: "refused" };
798 if (!found.value.can.edit) return { text: "You can't edit that doc for them, so nothing can be attached to it. Leave artifact out to make a new doc.", outcome: "refused" };
799 ref = found.value.folio;
800 if (!this.audience.shared || !found.value.audience_can_read) this.privateRead = true;
801 hidden = !found.value.audience_can_read;
802 } else {
803 const place = await this.whereFor(input.where, asker, folios);
804 if (!place.ok) return { text: place.message, outcome: "refused" };
805 const body = docBody(file.format, title, input);
806 const make = (where: FolioWhere) => folios.create(asker, { kind: "doc", title, markdown: body, template_id: null, where, parent_id: null, source: null });
807 let created = await make(place.where);
808 if (!created.ok && place.fallback && created.code === "forbidden") created = await make("private");
809 if (!created.ok) return { text: `The doc to keep it in couldn't be made: ${created.message}`, outcome: "refused" };
810 ref = created.value;
811 if (this.othersHere(asker)) {
812 const check = await folios.read(asker, audience, ref.id).catch(() => null);
813 hidden = !check?.ok || !check.value.audience_can_read;
814 }
815 }
816 const kept = await folios.attach(asker, ref.id, { name: file.name, content_type: file.content_type, bytes: file.bytes });
817 if (!kept.ok) return { text: `The file was made but couldn't be kept: ${kept.message}`, outcome: "refused" };
818 const size = sizeLabel(kept.value.bytes);
819 // The doc links its file, so whoever opens the doc finds it.
820 await folios
821 .edit(asker, ref.id, { kind: "doc", target: { kind: "append" }, markdown: `**File:** [${kept.value.name.replace(/[[\]]/g, "")}](${kept.value.url}) (${size})`, note: `Attached ${kept.value.name}`, suggest_only: false, marks_current: false })
822 .catch(() => null);
823 if (hidden) return this.notForEveryone(asker, ref, folios, "made");
824 return {
825 text: `Made ${kept.value.name} (${file.summary}, ${size}) and kept it with the doc ${ref.title} (${ref.path}). Give them the file's link, ${kept.value.url}, and the doc's if it helps.`,
826 outcome: "allowed",
827 };
828 }
829
830 /**
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.831 * Where a new artifact goes:
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.832 * - a space named by its name or id, among those everyone here can read;
833 * - "private": the asker's Private;
834 * - "conversation": Private, plus `view` for this conversation's people;
835 * - nothing: the conversation in a DM or private channel, and in a public
836 * channel the General space if the asker can add there (`fallback`:
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.837 * their Private if the artifacts service says they can't).
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.838 */
839 private async whereFor(given: unknown, asker: User, folios: FoliosPorts): Promise<{ ok: true; where: FolioWhere; fallback: boolean } | { ok: false; message: string }> {
840 const named = typeof given === "object" && given !== null ? (given as { space?: unknown }).space : given;
841 const wanted = typeof named === "string" ? named.trim().slice(0, 200) : "";
842 const others = this.audience.members.filter((m) => m.id !== asker.id).map((m) => m.id);
843 const byDefault = async (): Promise<{ ok: true; where: FolioWhere; fallback: boolean }> => {
844 if (!this.audience.shared) return { ok: true, where: others.length ? { conversation: this.audience.members.map((m) => m.id) } : "private", fallback: false };
845 const spaces = (await folios.spaces(asker, this.folioAudience())) ?? [];
846 const general = spaces.find((s) => s.slug === "general") ?? spaces.find((s) => s.name.toLowerCase() === "general");
847 return general && general.can.suggest ? { ok: true, where: { space_id: general.id }, fallback: true } : { ok: true, where: "private", fallback: false };
848 };
849 if (!wanted) return byDefault();
850 const lower = wanted.toLowerCase();
851 if (typeof given === "string" && lower === "private") return { ok: true, where: "private", fallback: false };
852 // A public channel has no list of people to share with: its default instead.
853 if (typeof given === "string" && lower === "conversation") return byDefault();
854 const space = findSpace((await folios.spaces(asker, this.folioAudience())) ?? [], wanted);
855 if (space) return { ok: true, where: { space_id: space.id }, fallback: false };
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.856 // An id the asker gave, for a space not everyone here can read: the artifacts service checks it.
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.857 if (/^spc_[A-Za-z0-9]+$/.test(wanted)) return { ok: true, where: { space_id: wanted }, fallback: false };
858 return { ok: false, message: `There's no space called ${wanted} that everyone here can read. Use list_spaces, or put it in "private" or "conversation".` };
859 }
860
861 /**
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.862 * An artifact someone here can't read: the link goes to the asker
863 * directly, and the agent says only that it found or made something,
864 * never what.
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.865 */
866 private async notForEveryone(asker: User, folio: FolioRef, folios: FoliosPorts, what: "found" | "made"): Promise<ToolResult> {
867 this.notHere.add(folio.id);
868 const who = `@${asker.username}`;
869 const note =
870 what === "made"
871 ? "I made this for you. Not everyone in the conversation you asked from can open it, so here is the link:"
872 : "Here is what you asked about. Not everyone in the conversation you asked from can open it, so here is the link:";
873 const sent = await folios.sendLink(asker, { title: folio.title, path: folio.path }, note).catch(() => false);
874 const lead = `Not everyone in this conversation can read this artifact, so ${what === "made" ? "it" : "its content"} isn't shown here. Don't quote it, name it or describe it here;`;
875 const text = sent
876 ? `${lead} say you ${what} it and that you've sent the link to ${who} directly.`
877 : what === "made"
878 ? `${lead} say you made it and that ${who} will find it under Artifacts, in their Private or shared with them.`
879 : `${lead} say you found it but can't share it here, and ask ${who} to message you directly.`;
880 return { text, outcome: what === "made" ? "allowed" : "withheld" };
881 }
882
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 asked883 /** Doing, not reading: memory, issues, sessions. Each refused unless offered. */
884 private async act(name: string, input: Record<string, unknown>): Promise<ToolResult> {
885 const actions = this.actions;
886 const offered = this.definitions().some((tool) => tool.name === name);
887 if (!actions || !offered) return { text: `There is no tool called ${name} here.`, outcome: "refused" };
888 const said = (answer: { ok: boolean; message: string }): ToolResult => ({ text: answer.message, outcome: answer.ok ? "allowed" : "refused" });
889 const text = (key: string, max: number) => String(input[key] ?? "").trim().slice(0, max);
890 switch (name) {
891 case "remember": {
892 const fact = text("fact", 2000);
893 if (!fact) return { text: "Say what to remember.", outcome: "refused" };
894 const scope = input.scope === "workspace" || input.scope === "channel" || input.scope === "person" ? input.scope : null;
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.895 return said(await actions.remember(fact, scope, this.privateRead));
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 asked896 }
897 case "forget":
898 return said(await actions.forget(text("id", 100)));
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar899 case "draft_issue": {
900 if (!this.audience.asker || !this.audience.codeAllowed()) return this.withheld();
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 asked901 const repo = await this.audience.repo(input.repo);
902 if (!repo) return this.withheld();
903 const title = text("title", 200);
904 const body = text("body", 20_000);
905 if (!title || !body) return { text: "An issue needs a title and a body.", outcome: "refused" };
906 const labels = Array.isArray(input.labels)
907 ? input.labels.filter((l): l is string => typeof l === "string").map((l) => l.trim()).filter(Boolean).slice(0, 5)
908 : [];
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar909 return said(await actions.draftIssue(repo, { title, body, labels }));
910 }
911 case "comment":
912 case "review_pull": {
913 const asker = this.audience.asker;
914 if (!asker || !this.audience.codeAllowed()) return this.withheld();
915 const repo = await this.audience.repo(input.repo);
916 if (!repo) return this.withheld();
917 const number = Math.floor(Number(input.number));
918 if (!Number.isFinite(number) || number < 1) return { text: "Give the issue or pull request's number.", outcome: "refused" };
919 const body = text("body", 20_000);
920 if (name === "comment") {
921 if (!body) return { text: "Say what to comment.", outcome: "refused" };
922 return said(await actions.comment!(repo, asker, number, body));
923 }
924 const verdict = input.verdict === "approve" || input.verdict === "request_changes" ? input.verdict : "comment";
925 if (!body && verdict !== "approve") return { text: "A review needs its text.", outcome: "refused" };
926 return said(await actions.review!(repo, asker, number, verdict, body));
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 asked927 }
928 case "start_session": {
929 const title = text("title", 120);
930 const goal = text("goal", 8000);
931 if (!title || !goal) return { text: "A session needs a title and a goal.", outcome: "refused" };
932 return said(await actions.startSession!(title, goal));
933 }
934 case "post_update": {
935 const note = text("text", 2000);
936 if (!note) return { text: "Say what to post.", outcome: "refused" };
937 if (this.updates >= 3) return { text: "You've posted enough updates for this step; carry on with the work.", outcome: "refused" };
938 this.updates++;
939 return said(await actions.postUpdate!(note));
940 }
941 case "use_subagent": {
942 const helper = text("name", 60).toLowerCase();
943 const brief = text("brief", 8000);
944 if (!helper || !brief) return { text: "Name the subagent and give it a brief.", outcome: "refused" };
945 return said(await actions.useSubagent!(helper, brief));
946 }
947 case "bring_in": {
948 const handle = text("handle", 60).replace(/^@/, "").toLowerCase();
949 const brief = text("brief", 8000);
950 if (!handle || !brief) return { text: "Name the colleague and give them a brief.", outcome: "refused" };
951 if (this.context.notConsult.includes(handle)) return { text: `You can't bring in @${handle} here: they sent you this work, or it is you.`, outcome: "refused" };
952 return said(await actions.bringIn!(handle, brief));
953 }
Merge branch 'worktree-agent-a1398e81ad1a64c5f'954 case "hand_off": {
955 const handle = text("handle", 60).replace(/^@/, "").toLowerCase();
956 const brief = text("brief", 8000);
957 if (!handle || !brief) return { text: "Name the colleague and give them a brief.", outcome: "refused" };
958 if (this.context.notConsult.includes(handle)) {
959 return { text: `You can't hand work to @${handle}: they sent you this work, or it is you. Answer with what you have.`, outcome: "refused" };
960 }
961 if (this.handOffs >= MAX_HAND_OFFS) return { text: `You've handed off to ${MAX_HAND_OFFS} colleagues from this message; hand off the rest later.`, outcome: "refused" };
962 const done = await actions.handOff!(handle, brief);
963 if (done.ok) this.handOffs++;
964 return said(done);
965 }
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 asked966 default:
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)967 return { text: `There is no tool called ${name}.`, outcome: "refused" };
968 }
969 }
970
971 private async code(name: string, input: Record<string, unknown>, viewer: User): Promise<ToolResult> {
972 if (name === "list_repositories") {
973 const repos = [...(await this.audience.repos()).values()];
974 if (!repos.length) return { text: "There are no repositories everyone here can read.", outcome: "allowed" };
975 return { text: untrusted("list_repositories", repos.map((repo) => `${repo.namespace}/${repo.name}${repo.isPrivate ? " (private)" : ""}`).join("\n")), outcome: "allowed" };
976 }
977 if (name === "search_code") {
978 const query = String(input.query ?? "").trim();
979 if (query.length < 2) return { text: "Search for at least two characters.", outcome: "refused" };
980 const only = input.repo === undefined || input.repo === null || input.repo === "" ? null : await this.audience.repo(input.repo);
981 if (input.repo && !only) return this.withheld();
982 const allowed = await this.audience.repos();
983 // Whatever search returns, only hits in allowed repositories come through.
984 const hits = (await this.ports.searchCode(viewer, query, only)).filter((hit) => allowed.has(hit.repo.toLowerCase()) && (!only || hit.repo.toLowerCase() === `${only.namespace}/${only.name}`.toLowerCase()));
985 if (!hits.length) return { text: "No code found.", outcome: "allowed" };
986 return { text: untrusted(`search_code "${query}"`, hits.slice(0, 10).map((hit) => `${hit.repo}:${hit.path}\n${hit.snippet}`).join("\n\n")), outcome: "allowed" };
987 }
988 if (name === "recent_activity") {
989 const one = input.repo ? await this.audience.repo(input.repo) : null;
990 if (input.repo && !one) return this.withheld();
991 const repos = one ? [one] : [...(await this.audience.repos()).values()].slice(0, 20);
992 if (!repos.length) return { text: "There are no repositories everyone here can read.", outcome: "allowed" };
993 const pulls = await this.ports.recentPulls(repos, viewer);
994 if (!pulls.length) return { text: "No recent pull requests.", outcome: "allowed" };
995 return { text: untrusted("recent_activity", pulls.map((p) => `${p.repo}#${p.number} ${p.status}: ${p.title} (${p.updated_at})`).join("\n")), outcome: "allowed" };
996 }
997 // The rest name one repository; it must be on the allow-list.
998 const repo = await this.audience.repo(input.repo);
999 if (!repo) return this.withheld();
1000 const full = `${repo.namespace}/${repo.name}`;
1001 switch (name) {
1002 case "read_file": {
1003 const path = String(input.path ?? "").trim().replace(/^\/+/, "");
1004 if (!path || path.split("/").some((part) => part === "..")) return { text: "Give a path inside the repository.", outcome: "refused" };
1005 const ref = typeof input.ref === "string" && input.ref.trim() ? input.ref.trim() : repo.defaultBranch;
1006 const file = await this.ports.readFile(repo, viewer, ref, path);
1007 if (!file) return { text: `No file ${path} at ${ref} in ${full}.`, outcome: "allowed" };
1008 if (file.text === null) return { text: `${full}:${path} is binary or too large to read (${file.size} bytes).`, outcome: "allowed" };
1009 return { text: untrusted(`${full}:${path}@${ref}`, file.text), outcome: "allowed" };
1010 }
1011 case "list_issues": {
1012 const state = input.state === "closed" ? "closed" : "open";
1013 const issues = await this.ports.listIssues(repo, viewer, state);
1014 if (!issues) return this.withheld();
1015 if (!issues.length) return { text: `No ${state} issues in ${full}.`, outcome: "allowed" };
1016 return { text: untrusted(`list_issues ${full}`, issues.slice(0, 30).map((i) => `#${i.number} [${i.state}] ${i.title}${i.labels.length ? ` (${i.labels.join(", ")})` : ""}`).join("\n")), outcome: "allowed" };
1017 }
1018 case "get_issue": {
1019 const issue = await this.ports.getIssue(repo, Math.floor(Number(input.number)), viewer);
1020 if (!issue) return { text: `No such issue in ${full}.`, outcome: "allowed" };
1021 const comments = issue.comments.map((c) => `@${c.author}: ${c.body}`).join("\n\n");
1022 return { text: untrusted(`${full}#${issue.number}`, `#${issue.number} [${issue.state}] ${issue.title}\n\n${issue.body}${comments ? `\n\nComments:\n\n${comments}` : ""}`), outcome: "allowed" };
1023 }
1024 case "get_pull": {
1025 const pull = await this.ports.getPull(repo, Math.floor(Number(input.number)), viewer);
1026 if (!pull) return { text: `No such pull request in ${full}.`, outcome: "allowed" };
1027 return { text: untrusted(`${full}#${pull.number}`, `#${pull.number} [${pull.status}] ${pull.title}\n\n${pull.body}${pull.checks ? `\n\nChecks: ${pull.checks}` : ""}`), outcome: "allowed" };
1028 }
1029 default:
1030 return { text: `There is no tool called ${name}.`, outcome: "refused" };
1031 }
1032 }
1033}
1034
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.1035/** What an agent hears when it asks for a kind that isn't built yet. */
1036const NOT_YET = "Slides, designs and dashboards aren't available yet: only docs can be made for now. Say so, and offer to write it as a doc instead.";
1037
1038/**
1039 * A folio id from an id or any artifact link (`/acme/-/artifacts/runbook-fol_…`,
1040 * with or without the site and a query); null when there is none.
1041 */
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.1042/**
1043 * What a doc made to hold a file says: the Markdown the file was made from
1044 * (less a first heading repeating the title, which the doc has), or a
1045 * preview of a spreadsheet's rows.
1046 */
1047export function docBody(format: MakeFileFormat, title: string, input: Record<string, unknown>): string {
1048 let body = "";
1049 if (format === "xlsx" || format === "csv") {
1050 const read = readSheets(input.sheets);
1051 if (read.ok) body = read.sheets.map((sheet) => `${read.sheets.length > 1 ? `## ${sheet.name}\n\n` : ""}${previewTable(sheet)}`).join("\n\n");
1052 } else {
1053 const markdown = String(input.content ?? "");
1054 const first = /^\s*#\s+(.+?)\s*#*\s*(?:\n|$)/.exec(markdown);
1055 body = (first && first[1]!.trim().toLowerCase() === title.trim().toLowerCase() ? markdown.slice(first[0].length).trimStart() : markdown).slice(0, 100_000);
1056 }
1057 return body.trim() ? body : "The file is attached below.";
1058}
1059
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.1060export function folioRef(given: string): string | null {
1061 const last = given.trim().split(/[?#]/)[0].split("/").filter(Boolean).at(-1) ?? "";
1062 return folioIdFrom(last);
1063}
1064
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.1065/** Where an artifact was written up from: a thread's link, cut to the site path the artifacts service keeps; null when it isn't one. */
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.1066export function sourceLink(given: string): { title: string; href: string } | null {
1067 let href = given.trim();
1068 if (!href) return null;
1069 if (/^https?:\/\//i.test(href)) {
1070 try {
1071 const url = new URL(href);
1072 href = `${url.pathname}${url.search}${url.hash}`;
1073 } catch {
1074 return null;
1075 }
1076 }
1077 return href.startsWith("/") && !href.startsWith("//") ? { title: "A conversation", href } : null;
1078}
1079
1080/** A space by its id, address or name (any case), among those given. */
1081function findSpace(spaces: FolioSpaceLine[], given: string): FolioSpaceLine | null {
1082 const wanted = given.trim().toLowerCase();
1083 return spaces.find((s) => s.id === given.trim()) ?? spaces.find((s) => s.slug.toLowerCase() === wanted) ?? spaces.find((s) => s.name.toLowerCase() === wanted) ?? null;
1084}
1085
1086function spaceLine(s: FolioSpaceLine): string {
1087 const can = s.can.edit ? "you can edit" : s.can.suggest ? "you can suggest edits" : "read only";
1088 const projects = s.projects.length ? `; about ${s.projects.join(", ")}` : "";
1089 return `- ${s.name} (id ${s.id}, ${s.kind}; ${can}${projects})${s.description ? `: ${s.description}` : ""}`;
1090}
1091
1092/** An artifact as the agent reads it: where it is, what it may do, and its content (a doc's Markdown, with its top-level block ids). */
1093export function folioReadText(read: FolioAgentRead): string {
1094 const f = read.folio;
1095 const can = read.can.edit ? "you can edit it" : read.can.suggest ? "you can suggest edits" : "you can only read it";
1096 const where = read.space ? `in the ${read.space.name} space` : "not in a space";
1097 const blocks = read.blocks?.length ? `\nTop-level blocks: ${read.blocks.map((b) => `${b.id} ${b.type}${b.level ? ` ${b.level}` : ""}`).join(", ")}` : "";
1098 return `# ${f.title} (${f.path}, id ${f.id})\nA ${f.kind}, ${where}; ${can}. Edited ${f.edited_at.slice(0, 16)}.${blocks}\n\n${read.content}`;
1099}
1100
1101/** What an edit did, naming the artifact only where everyone here can read it. */
1102function editMessage(result: FolioAgentEditResult, name: boolean): string {
1103 const on = name ? ` on ${result.folio.title} (${result.folio.path})` : "";
1104 switch (result.mode) {
1105 case "applied":
1106 return `Changed${on}. It's in its history as yours.`;
1107 case "suggested":
1108 return `Suggested${on}: people accept or reject it there.${name ? " Link it so they can." : ""}`;
1109 case "proposed":
1110 return `Proposed a change${on}: a person previews and applies it.`;
1111 }
Docs: a workspace knowledge base people and agents write together1112}
1113
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)1114function messageLine(m: FoundMessage): string {
1115 const where = m.channel ? `#${m.channel}` : "a direct message";
1116 return `[${m.created_at.slice(0, 16)} in ${where}, channel ${m.channel_id}, message ${m.id}] @${m.author}: ${m.body}`;
1117}

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