Skip to content
302 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 * The reply's audience and tools, wired to g1t's services. Each call here
3 * runs only after `Audience` and `ToolBox` decided it may (audience.ts,
4 * tools.ts); the backing services check the viewer again, as they do for
5 * every request.
6 */
7import {
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.8 type Result,
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)9 type ServiceBinding,
10 type User,
11 chatClient,
Docs: a workspace knowledge base people and agents write together12 docsClient,
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.13 foliosClient,
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)14 identityClient,
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.15 notifyClient,
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)16 reposClient,
17 searchClient,
18 workClient,
19} from "@g1t/contracts";
20
21import type { AudiencePorts, RepoRef } from "./audience.ts";
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.22import type { FolioDone, FoliosPorts, FoundMessage, ToolPorts } from "./tools.ts";
23import { RECALL_LIMIT, passageSource } from "./recall.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 { base64 } from "./files.ts";
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.25import type { TeamAgentInfo, TeamsHere } from "./teammates.ts";
26
27export type TeamsEnv = { DB: D1Database; IDENTITY: ServiceBinding; NOTIFY?: ServiceBinding };
28
29/**
30 * The teams an agent is on (teammates.ts), with who is on them and how
31 * they show now. Null when identity can't be asked; presence is left out
32 * when notify can't be (then no one reads as offline).
33 */
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.34export async function loadTeams(env: TeamsEnv, slug: string, workspaceId: string, agent: { id: string }): Promise<TeamsHere | null> {
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.35 const teams = await identityClient(env.IDENTITY)
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.36 .agentTeams(slug, agent.id)
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.37 .catch(() => null);
38 if (!teams) return null;
39 if (!teams.length) return { teams: [], agents: [], presence: [] };
40 const userIds = [...new Set(teams.flatMap((team) => team.people.map((person) => person.user_id)))];
41 const added = [...new Set(teams.flatMap((team) => team.agent_ids))].slice(0, 90);
42 const [presence, rows] = await Promise.all([
43 env.NOTIFY && userIds.length ? notifyClient(env.NOTIFY).workspacePresence(slug, userIds).catch(() => null) : Promise.resolve(null),
44 env.DB.prepare(
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.45 `SELECT id, handle, display_name, title FROM agents
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.46 WHERE workspace_id = ?1 AND archived_at IS NULL AND scope = 'workspace'
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.47 AND id IN (SELECT value FROM json_each(?2))
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.48 ORDER BY builtin DESC, handle LIMIT 200`,
49 )
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.50 .bind(workspaceId, JSON.stringify(added))
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.51 .all<TeamAgentInfo>(),
52 ]);
53 return { teams, agents: rows.results, presence: presence ?? [] };
54}
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)55
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.56/**
57 * The visible teams each of the workspace's agents is on, by agent id, as
58 * team names: for the roster colleagues and @g1t are told. Empty when
59 * identity can't be asked.
60 */
61export async function teamsOfAgents(env: { IDENTITY: ServiceBinding }, slug: string): Promise<Map<string, string[]>> {
62 const out = new Map<string, string[]>();
63 const teams = await identityClient(env.IDENTITY)
64 .teamAgentIndex(slug)
65 .catch(() => null);
66 for (const team of teams ?? []) for (const id of team.agent_ids) out.set(id, [...(out.get(id) ?? []), team.name]);
67 return out;
68}
69
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)70export type PortsEnv = {
71 DB: D1Database;
72 CHAT: ServiceBinding;
73 IDENTITY: ServiceBinding;
74 REPOS: ServiceBinding;
75 WORK: ServiceBinding;
76 SEARCH: ServiceBinding;
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.77 /** The artifacts service, for agents reading and writing artifacts; absent on an installation without it. */
78 ARTIFACTS?: ServiceBinding;
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.79 /**
80 * Where files kept with artifacts are served (g1tusercontent.com for
81 * g1t.sh), for the links to files agents make. Empty: the site's own
82 * `/-/usercontent` path.
83 */
84 USERCONTENT_URL?: string;
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)85};
86
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.87/** The usercontent origin files are linked under, without a trailing slash. */
88export function usercontentBase(env: { USERCONTENT_URL?: string }): string {
89 return (env.USERCONTENT_URL ?? "").trim().replace(/\/+$/, "") || "/-/usercontent";
90}
91
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)92const refOf = (repo: { id: string; namespace: string; name: string; isPrivate: boolean; defaultBranch: string; forkOf?: string | null }): RepoRef => ({
93 id: repo.id,
94 namespace: repo.namespace,
95 name: repo.name,
96 isPrivate: repo.isPrivate,
97 defaultBranch: repo.defaultBranch,
98 forkOf: repo.forkOf ?? null,
99});
100
101export function audiencePorts(env: PortsEnv, workspace: string, channelId: string): AudiencePorts {
102 const repos = reposClient(env.REPOS);
103 return {
104 async info() {
105 const found = await chatClient(env.CHAT).audience(workspace, channelId);
106 if (!found.ok) throw new Error(`the conversation's audience could not be read: ${found.error.message}`);
107 return found.value;
108 },
109 users: (ids) => identityClient(env.IDENTITY).usersForAudience(ids),
110 workspaceRepos: async (viewer) => (await repos.list(viewer, { namespace: workspace, memberOnly: true })).map(refOf),
111 readable: async (ids, viewer) => (await repos.readable(ids.slice(0, 500), viewer)).map(refOf),
112 };
113}
114
115const found = (m: { channel_id: string; channel: string | null; message: { id: string; author: { name: string }; body: string; created_at: string; card: { title: string } | null } }): FoundMessage => ({
116 channel: m.channel,
117 channel_id: m.channel_id,
118 id: m.message.id,
119 author: m.message.author.name,
120 body: m.message.body || (m.message.card ? `[card: ${m.message.card.title}]` : ""),
121 created_at: m.message.created_at,
122});
123
124export function toolPorts(
125 env: PortsEnv,
126 workspace: string,
127 workspaceId: string,
128 channelId: string,
129 consult: ToolPorts["consult"],
Docs: a workspace knowledge base people and agents write together130 agentId: string | 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)131): ToolPorts {
132 const repos = reposClient(env.REPOS);
133 const work = workClient(env.WORK);
134 const path = (repo: RepoRef) => ({ namespace: repo.namespace, name: repo.name });
135 return {
136 async readFile(repo, viewer, ref, file) {
137 const blob = await repos.blob(path(repo), viewer, ref, file);
138 return blob.ok ? { text: blob.value.text, size: blob.value.size } : null;
139 },
140 async searchCode(viewer, query, repo) {
141 const results = await searchClient(env.SEARCH).search(viewer, repo ? `${query} repo:${repo.namespace}/${repo.name}` : query, { type: "code", perPage: 20 });
142 if (!results.ok) return [];
143 return results.value.hits
144 .filter((hit) => hit.kind === "code" && hit.repo && hit.path)
145 .map((hit) => ({
146 repo: hit.repo!,
147 path: hit.path!,
148 snippet: hit.lines.map((line) => `${line.number}: ${line.parts.map((part) => part.text).join("")}`).join("\n"),
149 }));
150 },
151 async listIssues(repo, viewer, state) {
152 const issues = await work.listIssues(path(repo), viewer, { state });
153 return issues.ok ? issues.value.map((i) => ({ number: i.number, title: i.title, state: i.state, labels: i.labels })) : null;
154 },
155 async getIssue(repo, number, viewer) {
156 const detail = await work.getIssue(path(repo), number, viewer);
157 if (!detail.ok) return null;
158 const { issue, comments } = detail.value;
159 return {
160 number: issue.number,
161 title: issue.title,
162 state: issue.state,
163 body: issue.body,
164 comments: comments.filter((c) => c.kind === "comment").slice(-20).map((c) => ({ author: c.author.username, body: c.body })),
165 };
166 },
167 async getPull(repo, number, viewer) {
168 const detail = await work.getPull(path(repo), number, viewer);
169 if (!detail.ok) return null;
170 const { pull, checks } = detail.value;
171 return {
172 number: pull.number,
173 title: pull.title,
174 status: pull.status,
175 body: pull.body ?? "",
176 checks: checks ? `${checks.status}${checks.results.length ? `: ${checks.results.map((r) => `${r.command} ${r.passed ? "passed" : "failed"}`).join(", ")}` : ""}` : null,
177 };
178 },
179 async recentPulls(list, viewer) {
180 const byId = new Map(list.map((repo) => [repo.id, repo]));
181 const groups = await work.pullsForRepos([...byId.keys()], viewer, 5);
182 return groups
183 .flatMap((group) => [...group.open, ...group.closed].map((pull) => ({ pull, repo: byId.get(group.repoId) })))
184 .filter((entry) => entry.repo)
185 .map(({ pull, repo }) => ({
186 repo: `${repo!.namespace}/${repo!.name}`,
187 number: pull.number,
188 title: pull.title,
189 status: pull.status,
190 updated_at: pull.mergedAt ?? pull.updatedAt,
191 }))
192 .sort((a, b) => b.updated_at.localeCompare(a.updated_at))
193 .slice(0, 25);
194 },
195 async searchMessages(query) {
196 const result = await chatClient(env.CHAT).searchForAgent(workspace, channelId, query, 20);
197 return result.ok ? result.value.map(found) : null;
198 },
199 async readThread(target, id) {
200 const result = await chatClient(env.CHAT).threadForAgent(workspace, channelId, target, id);
201 return result.ok ? result.value.map(found) : null;
202 },
203 async roster(viewer: User | null) {
204 const identity = identityClient(env.IDENTITY);
205 const [members, teams, agents] = await Promise.all([
206 viewer ? identity.listMembers(workspace, viewer).catch(() => null) : Promise.resolve(null),
207 viewer ? identity.listTeams(viewer, workspace).catch(() => null) : Promise.resolve(null),
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.208 env.DB.prepare("SELECT handle, display_name, title, role FROM agents WHERE workspace_id = ? AND archived_at IS NULL AND scope = 'workspace' ORDER BY builtin DESC, handle")
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)209 .bind(workspaceId)
210 .all<{ handle: string; display_name: string; title: string; role: string }>(),
211 ]);
212 const people = members?.ok ? members.value.map((m) => `- @${m.username}${m.name ? ` (${m.name})` : ""}, ${m.role}`).join("\n") : "(people could not be listed)";
213 const teamLines = teams?.ok ? teams.value.map((t) => `- ${t.slug}: ${t.name}${t.description ? `, ${t.description}` : ""}`).join("\n") : "";
214 const agentLines = agents.results.map((a) => `- @${a.handle} (${a.display_name}): ${a.title || a.role}`).join("\n");
215 return `People:\n${people}${teamLines ? `\n\nTeams:\n${teamLines}` : ""}\n\nAgents:\n${agentLines}`;
216 },
217 consult,
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.218 ...(env.ARTIFACTS && agentId ? { folios: folioPorts(env.ARTIFACTS, env.CHAT, workspace, agentId, usercontentBase(env)) } : {}),
Docs: a workspace knowledge base people and agents write together219 };
220}
221
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.222/** A call's result as the tools take it: the value, or the error's code and sentence. */
223const done = <T>(result: Result<T>): FolioDone<T> => (result.ok ? result : { ok: false, code: result.error.code, message: result.error.message });
224
225/**
226 * Artifacts (folios) as an agent reads and writes them: Markdown in,
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.227 * Markdown out, every call checked by the artifacts service. Spaces still come
228 * from the artifacts service's `spaces_for_agent`: spaces are shared by pages
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.229 * and folios, and have no folio method of their own.
230 */
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.231function folioPorts(docs: ServiceBinding, chatBinding: ServiceBinding, workspace: string, agentId: string, usercontent: string): FoliosPorts {
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.232 const folios = foliosClient(docs);
233 const where = (f: { title: string; path: string; id: string }) => `${f.title} (${f.path}, id ${f.id})`;
Docs: a workspace knowledge base people and agents write together234 return {
235 async spaces(viewer, audience) {
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.236 const found = await docsClient(docs).spacesForAgent(workspace, agentId, viewer, audience);
Docs: a workspace knowledge base people and agents write together237 if (!found.ok) return 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.238 return found.value.map((s) => ({ id: s.id, slug: s.slug, name: s.name, description: s.description, kind: s.kind, projects: s.projects ?? [], can: s.can }));
Docs: a workspace knowledge base people and agents write together239 },
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.240 async recall(viewer, audience, query, spaces, kinds) {
241 const found = await folios.recallForAgent(workspace, agentId, viewer, { query, limit: RECALL_LIMIT, spaces, kinds: kinds ?? null }, audience);
Agents recall what Docs say before they answer or work, and each has required reading242 return found.ok ? found.value : null;
243 },
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.244 async search(viewer, audience, input) {
245 const kinds = input.kind ? [input.kind] : null;
246 // Passages too, when the search isn't narrowed to a space or project: recall can't be.
247 const [list, passages] = await Promise.all([
248 folios.foliosForAgent(workspace, agentId, viewer, { tab: "all", q: input.query, kinds, space_id: input.space_id, project: input.project, limit: 10 }, audience),
249 input.space_id || input.project ? Promise.resolve(null) : folios.recallForAgent(workspace, agentId, viewer, { query: input.query, limit: 4, kinds }, audience).catch(() => null),
250 ]);
251 if (!list.ok) return null;
252 const lines = list.value.items.map((f) => {
253 const space = f.space ? `in ${f.space.name}` : "not in a space";
254 return `- ${where(f)}: a ${f.kind} ${space}, edited ${f.edited_at.slice(0, 10)}${f.stale ? ", possibly out of date" : ""}${f.excerpt ? `: ${f.excerpt.replace(/\s+/g, " ").slice(0, 300)}` : ""}`;
255 });
256 const found = passages?.ok ? passages.value.map((p) => `### ${passageSource(p)}\n${p.text.trim().slice(0, 800)}`) : [];
257 if (!lines.length && !found.length) return "No artifacts found.";
258 return [lines.length ? lines.join("\n") : "No artifacts matched by title or words.", ...(found.length ? ["", "Passages that match:", "", found.join("\n\n")] : [])].join("\n");
Docs: a workspace knowledge base people and agents write together259 },
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.260 read: async (viewer, audience, folioId) => done(await folios.readForAgent(workspace, agentId, viewer, folioId, audience)),
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store261 async stale(viewer, audience, repo) {
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.262 const found = await folios.staleForAgent(workspace, agentId, viewer, { repo }, audience);
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store263 if (!found.ok) return 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.264 if (!found.value.length) return "No artifacts are marked possibly out of date.";
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store265 return found.value
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.266 .map((f) => {
267 const can = f.viewer_role === "edit" || f.viewer_role === "manage" ? (f.agent_mode === "edit" ? "you can edit" : "you can suggest") : f.viewer_role === "comment" ? "you can suggest" : "read only";
268 return `- ${where(f)}, a ${f.kind} ${f.space ? `in ${f.space.name}` : "not in a space"} (${can}), edited ${f.edited_at.slice(0, 10)}`;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store269 })
270 .join("\n");
271 },
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.272 async create(viewer, input) {
273 const made = await folios.createAsAgent(workspace, agentId, viewer, {
274 kind: input.kind,
275 title: input.title,
276 content: input.markdown === null ? null : { markdown: input.markdown },
277 template_id: input.template_id,
278 where: input.where,
279 parent_id: input.parent_id,
280 source: input.source,
281 });
282 return done(made);
283 },
284 edit: async (viewer, folioId, edit) => done(await folios.editAsAgent(workspace, agentId, viewer, folioId, edit)),
285 async share(viewer, audience, folioId, userIds, role) {
286 const shared = await folios.shareAsAgent(workspace, agentId, viewer, folioId, { user_ids: userIds, role }, audience);
287 return shared.ok ? { ok: true, value: null } : done(shared);
Docs: a workspace knowledge base people and agents write together288 },
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.289 async attach(viewer, folioId, file) {
290 const kept = await folios.attachAsAgent(workspace, agentId, viewer, folioId, { name: file.name, content_type: file.content_type, data: base64(file.bytes) });
291 return kept.ok ? { ok: true, value: { url: `${usercontent}${kept.value.url}`, name: kept.value.name, bytes: kept.value.bytes } } : done(kept);
292 },
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.293 async sendLink(asker, link, note) {
294 const chat = chatClient(chatBinding);
295 const dm = await chat.openDm(workspace, asker, [{ kind: "agent", id: agentId }]);
296 if (!dm.ok) return false;
297 // No mention, so it wakes nobody.
298 const posted = await chat.postAsAgent(workspace, dm.value.id, agentId, { body: `${note} [${link.title.replace(/[[\]]/g, "")}](${link.path})`, asked_by: asker.id });
299 return posted.ok;
Docs: a workspace knowledge base people and agents write together300 },
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)301 };
302}

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