Skip to content
698 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Chat and workspace agents: channels, DMs and named agents you talk to1/**
2 * A workspace's own agents: named members with a job, a personality,
3 * routing limits and a budget, kept by the agents service
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)4 * (`services/agents`). Every workspace also has `@g1t`, its built-in
5 * orchestrator, kept the same way (`builtin`). Plan: docs/WORKSPACE.md,
6 * "g1t, the orchestrator".
Chat and workspace agents: channels, DMs and named agents you talk to7 *
8 * Wire shapes are snake_case end to end.
9 */
10import type { ServiceBinding } from "./clients";
11import type { Role, User } from "./identity";
12import type { Result } from "./result";
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar13import type { CardActionResult } from "./chat";
Chat and workspace agents: channels, DMs and named agents you talk to14
15// Model tiers (`ModelTier`, `MODEL_TIERS`) are integrations.ts's, the
16// same ones runs are routed between.
17import type { ModelTier } from "./integrations";
18
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)19/** The built-in orchestrator's handle; nobody else's agent may take it. */
20export const BUILTIN_AGENT_HANDLE = "g1t";
21
22/** The built-in orchestrator's template id: not one a workspace can adopt. */
23export const ORCHESTRATOR_TEMPLATE = "orchestrator";
24
Chat and workspace agents: channels, DMs and named agents you talk to25/** Voice presets; free text in `personality` refines them. */
26export type PersonalityPreset = "crisp" | "friendly" | "socratic" | "terse";
27
28export type AgentRouting = {
29 /** Never route below this tier. Null: no floor. */
30 floor: ModelTier | null;
31 /** Never route above this tier. Null: no ceiling. */
32 ceiling: ModelTier | null;
33 /**
34 * Where its model calls may go. Empty: anything the workspace allows.
35 * `g1t`: g1t's hosted models. `workspace`: the workspace's own providers
36 * (Integrations), whichever they are. Any other entry: one of the
37 * workspace's own providers by integration id. Entries combine, so
38 * `["g1t", "workspace"]` is both.
39 */
40 providers: string[];
41 /** Advanced: a fixed `provider/model`, for own endpoints. Usually null. */
42 pinned: string | null;
43};
44
45export type AgentBudget = {
46 /** Monthly cap in micro-dollars. Null: only the workspace limit applies. */
47 monthly_micros: number | null;
48 daily_micros: number | null;
49 /** Default cap for one task. */
50 task_micros: number | null;
51};
52
53export type AgentAutonomy = {
54 open_pull_requests: "alone" | "approval";
55 merge: "alone" | "approval" | "never";
56 deploy_production: "approval" | "never";
57 edit_docs: "alone" | "suggest";
58};
59
60export type AgentStatus = "idle" | "working" | "waiting" | "out_of_budget" | "paused";
61
62export type WorkspaceAgent = {
63 id: string;
64 workspace_id: string;
65 /** Lowercase, unique in the workspace, never `g1t`. Mentioned as `@handle`. */
66 handle: string;
67 display_name: string;
68 /** Uploaded avatar hash, or null for the generated mark. */
69 avatar: string | null;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)70 /**
71 * What its generated avatar is drawn from: a little pixel creature, the
72 * same for the same seed everywhere. Set from the handle when it is
73 * made; changing it gives the agent a new face.
74 */
75 avatar_seed: string;
76 /**
77 * One line, as lists show it: "QA Engineer on the QA team". Made from
78 * the title and team (or department) when not written.
79 */
Chat and workspace agents: channels, DMs and named agents you talk to80 role: 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)81 /**
82 * Agents are hired into roles, not tasks (docs/WORKSPACE.md, "Roles, not
83 * tasks"): a title, a team, and broad responsibilities.
84 */
85 title: string;
86 /** The team it is on, by slug, from the workspace's teams; null for none. */
87 team: string | null;
88 /** A label for where it works when it is on no team: "QA", "Sales". */
89 department: string;
90 /** What it is responsible for: 2 to 8 short duties, or none yet. */
91 responsibilities: string[];
92 /**
93 * Specialised help it will use inside its own work. Never members, never
94 * wider than their agent. Stored now; they run with tasks and sessions.
95 */
96 subagents: SubagentDef[];
97 /**
98 * Who it works with: `internal`, the workspace's own people (back
99 * office), or `customers` (front office). Only `internal` for now.
100 */
101 faces: AgentFaces;
Chat and workspace agents: channels, DMs and named agents you talk to102 /** The job: what it is responsible for and how it works. */
103 instructions: string;
104 personality_preset: PersonalityPreset;
105 /** Free text refining the voice. Never changes what it may do. */
106 personality: string;
107 routing: AgentRouting;
108 budget: AgentBudget;
109 autonomy: AgentAutonomy;
110 /** Tasks it works at once; more queue on its desk. */
111 capacity: number;
112 /** The template it was made from, if any. */
113 template: string | null;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)114 /**
115 * The workspace's built-in orchestrator, `@g1t`: every workspace has one,
116 * made the first time its agents are asked for. It cannot be archived,
117 * and its handle, name, role and job are fixed; its `instructions` are
118 * added to that job. Listed first.
119 */
120 builtin: boolean;
Chat and workspace agents: channels, DMs and named agents you talk to121 version: number;
122 status: AgentStatus;
123 /** Spend this calendar month, in micro-dollars. */
124 spent_month_micros: number;
125 created_by: string;
126 created_at: string;
127 updated_at: string;
128 archived_at: string | null;
129};
130
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/**
132 * Back office or front office (docs/WORKSPACE.md, "Back office and front
133 * office"). Customer-facing agents are not available yet.
134 */
135export type AgentFaces = "internal" | "customers";
136
137/**
138 * A subagent: help an agent keeps for its own work, such as Margo's
139 * `flake-hunter`. Its routing limits sit within its agent's: a floor below
140 * the agent's is raised to it, a ceiling above is lowered to it.
141 */
142export type SubagentDef = {
143 /** Lowercase letters, digits and hyphens: `flake-hunter`. Unique on the agent. */
144 name: string;
145 /** One line: what it is for. */
146 description: string;
147 instructions: string;
148 routing: { floor: ModelTier | null; ceiling: ModelTier | null };
149 /** How many of it may run at once inside one task, 1 to 8. */
150 max_parallel: number;
151};
152
Chat and workspace agents: channels, DMs and named agents you talk to153export type NewWorkspaceAgent = {
154 handle: string;
155 display_name: 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)156 /** Left out or empty: made from the title and team. */
157 role?: string;
158 title?: string;
159 team?: string | null;
160 department?: string;
161 responsibilities?: string[];
162 subagents?: SubagentDef[];
163 /** Only `internal` for now; `customers` is refused. */
164 faces?: AgentFaces;
Chat and workspace agents: channels, DMs and named agents you talk to165 instructions: string;
166 personality_preset?: PersonalityPreset;
167 personality?: string;
168 routing?: Partial<AgentRouting>;
169 budget?: Partial<AgentBudget>;
170 autonomy?: Partial<AgentAutonomy>;
171 capacity?: number;
172 template?: string | null;
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)173 /** Its avatar's seed; left out, the handle. */
174 avatar_seed?: string;
Chat and workspace agents: channels, DMs and named agents you talk to175};
176
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)177/**
178 * A role to hire an agent into, by department. Agents get names, not job
179 * titles ("Margo", the QA Engineer).
180 */
Chat and workspace agents: channels, DMs and named agents you talk to181export type AgentTemplate = {
182 id: 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)183 /** The name it suggests first. */
Chat and workspace agents: channels, DMs and named agents you talk to184 display_name: string;
185 handle: 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)186 /** Other names that suit it, for the form's shuffle. Each is also a valid handle, lowercased. */
187 name_ideas: string[];
Chat and workspace agents: channels, DMs and named agents you talk to188 role: 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)189 title: string;
190 department: string;
191 responsibilities: string[];
192 subagents: SubagentDef[];
Chat and workspace agents: channels, DMs and named agents you talk to193 instructions: string;
194 personality_preset: PersonalityPreset;
195 routing: AgentRouting;
196};
197
198/** What the chat service hands an agent: a message it should answer. */
199export type AgentDelivery = {
200 workspace: string;
201 workspace_id: string;
202 channel_id: string;
203 channel_kind: "channel" | "dm";
204 channel_name: string | null;
205 agent_id: string;
206 /** The message that woke it. */
207 message_id: string;
208 thread_root: string | null;
209 /** Who asked: the person's user id. */
210 asked_by: string;
211 /** Agent-to-agent hops so far in this chain. */
212 hops: number;
213 /**
214 * What the person who asked may do, from the viewer the chat service
215 * already holds when the message is posted (`askerAccess`). An agent
216 * never does more for someone than they could do themselves
217 * (docs/WORKSPACE.md, "The whole company"). Absent from an older chat
218 * service: the agent then treats the asker as unable to change code.
219 */
220 asker?: AskerAccess | null;
221 /**
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)222 * The agents that handled this request before this one, by id, oldest
223 * first; the last sent the work here. An agent never hands back or
224 * consults the one that sent it work, and the hop limit counts every
225 * hand-off and consult along the chain. Absent: none (a person asked).
226 */
227 chain?: string[];
228 /**
Chat and workspace agents: channels, DMs and named agents you talk to229 * Where the conversation is: g1t's own chat, or later another chat app
230 * the workspace connected (docs/WORKSPACE.md, "Working from another chat
231 * app"). The agent reads and replies through that surface; its
232 * definition, budget and replies are the same everywhere. Absent: `g1t`.
233 */
234 surface?: AgentSurface;
235};
236
237/** The chat surfaces an agent answers on. Only g1t's own today. */
238export type AgentSurface = "g1t";
239
240/** Who asked an agent, as far as its reply needs to know. */
241export type AskerAccess = {
242 username: string;
243 /** Their role in the workspace; `outside` for someone who is not a member. */
244 role: Role | "outside";
245 /**
246 * Whether they can change code in the workspace: Code is on for them
247 * and they hold write access (or more) on at least one of its
248 * repositories, through the base permission or a grant.
249 */
250 can_write: boolean;
251};
252
253const WRITING_ROLES = new Set(["write", "maintain", "admin"]);
254
255/**
256 * `user`'s access in `workspace`, for `AgentDelivery.asker`. Pure, with no
257 * imports, so the chat service computes it from its viewer for free.
258 */
259export function askerAccess(user: User, workspace: string): AskerAccess {
260 const slug = workspace.toLowerCase();
261 const membership = user.workspaces?.find((m) => m.slug.toLowerCase() === slug);
262 // Someone who uses only Chat, Docs and agents sees no repository at all.
263 const code = membership?.code_access !== false;
264 const base = membership ? membership.role === "owner" || WRITING_ROLES.has(membership.base_permission ?? "write") : false;
265 const granted = (user.grants ?? []).some((grant) => grant.workspace.toLowerCase() === slug && WRITING_ROLES.has(grant.role));
266 return {
267 username: user.username,
268 role: membership?.role ?? "outside",
269 can_write: code && (base || granted),
270 };
271}
272
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 asked273/**
274 * A session: one bounded piece of work an agent took on (docs/WORKSPACE.md,
275 * "Sessions"). A conversation with an agent is not a session: talking stays
276 * cheap and quick, and when a request needs real work the agent spins off a
277 * session for it, with its own context, transcript, budget and live card in
278 * the conversation. Sessions start other sessions (one of the agent's
279 * subagents, or a colleague brought in), and everything a tree of sessions
280 * spends is charged to the agent at its root, so a chain never escapes the
281 * budget that started it.
282 */
283export type AgentSessionKind =
284 /** Spun off from a conversation: someone asked for work. */
285 | "chat"
286 /** A routine's run. */
287 | "routine"
288 /** A colleague brought in by another session. */
289 | "helper"
290 /** One of the agent's own subagents, inside another session. */
291 | "subagent";
292
293export type AgentSessionStatus =
294 | "queued"
295 | "working"
296 /** Waiting on sessions it started. */
297 | "waiting"
298 /** Stopped at its spend cap: someone who may raise it decides. */
299 | "needs_approval"
300 | "done"
301 | "failed"
302 | "stopped";
303
304/** The statuses of a session that is not over. */
305export const SESSION_LIVE: readonly AgentSessionStatus[] = ["queued", "working", "waiting", "needs_approval"];
306
307export type AgentSession = {
308 id: string;
309 workspace_id: string;
310 agent_id: string;
311 /** The agent's handle, name and face, for lists. */
312 agent_handle: string;
313 agent_name: string;
314 agent_avatar_seed: string;
315 /** The subagent running it, by name, when kind is `subagent`. */
316 subagent: string | null;
317 kind: AgentSessionKind;
318 /** The session that started it, and the root of its tree. */
319 parent_id: string | null;
320 root_id: string;
321 /** Whose budget pays for it: the agent at the root of its tree. */
322 payer_agent_id: string;
323 title: string;
324 goal: string;
325 status: AgentSessionStatus;
326 /** Why it is waiting, stopped or failed, in a line. */
327 status_note: string | null;
328 /** What it found or did, once done: its report. */
329 summary: string | null;
330 /** Where it reports: the conversation it was started from. */
331 channel_id: string;
332 channel_kind: "channel" | "dm";
333 channel_name: string | null;
334 /** Its live card in that conversation; its updates go in the card's thread. */
335 card_message_id: string | null;
336 asked_by: string | null;
337 asked_by_username: string | null;
338 routine_id: string | null;
339 steps: number;
340 tool_calls: number;
341 input_tokens: number;
342 output_tokens: number;
343 /** At list price, what it counts against budgets. */
344 charged_micros: number;
345 /** The most it may spend before someone approves more. */
346 cap_micros: number | null;
347 model: string | null;
348 /** What it produced: issues filed, sessions started. */
349 outputs: SessionOutput[];
350 created_at: string;
351 updated_at: string;
352 finished_at: string | null;
353 /**
354 * False when the viewer is not among the people of the conversation it
355 * came from: they see that it ran and what it cost, never its title,
356 * goal, report or transcript.
357 */
358 visible: boolean;
359};
360
361export type SessionOutput =
362 | { kind: "issue"; repo: string; number: number; title: string }
363 | { kind: "session"; id: string; agent_handle: string; title: string }
364 | { kind: "memory"; id: string; body: string };
365
366/** One entry of a session's transcript, as its page shows it. */
367export type SessionEvent = {
368 seq: number;
369 kind: "goal" | "text" | "tool" | "steer" | "update" | "child" | "result" | "note";
370 /** Who: the agent's handle, a person's username (steering), or null for g1t's notes. */
371 by: string | null;
372 body: string;
373 /** For `tool`: the tool, and whether it read, was withheld, refused or failed. */
374 tool: string | null;
375 outcome: string | null;
376 created_at: string;
377};
378
379export type AgentSessionDetail = {
380 session: AgentSession;
381 events: SessionEvent[];
382 /** Every session in its tree, root first. */
383 tree: AgentSession[];
384 /** Whether the viewer may stop it, steer it, or approve more spend. */
385 can_stop: boolean;
386 can_steer: boolean;
387 can_approve: boolean;
388};
389
390/**
391 * What an agent remembers (docs/WORKSPACE.md, "What an agent can and can't
392 * know"). Every fact carries where it came from, and its scope decides, in
393 * code, where it may be recalled and who may see it:
394 *
395 * - `workspace`: anywhere in the workspace. Owners write these, or an agent
396 * from a public channel, which every member can read already.
397 * - `channel`: only in that channel and its threads.
398 * - `person`: only in a direct message with that one person.
399 */
400export type AgentMemoryScope = "workspace" | "channel" | "person";
401
402export type AgentMemory = {
403 id: string;
404 agent_id: string;
405 scope: AgentMemoryScope;
406 /** The channel's id or the person's user id; empty for `workspace`. */
407 scope_ref: string;
408 /** The channel's name or the person's username, for display. */
409 scope_label: string | null;
410 body: string;
411 source_kind: "message" | "session" | "person";
412 /** A message id, a session id, or the username of who wrote it. */
413 source_ref: string | null;
414 source_label: string | null;
415 /** The channel the source is in, for a link. */
416 source_channel_id: string | null;
417 created_by: string;
418 created_by_kind: "agent" | "user";
419 pinned: boolean;
420 created_at: string;
421 updated_at: string;
422};
423
424/** When a routine runs, in UTC. */
425export type RoutineSchedule = {
426 every: "hour" | "day" | "weekday" | "week";
427 /** Minute of the hour, 0 to 59. */
428 minute: number;
429 /** Hour of the day (UTC), 0 to 23; not used for `hour`. */
430 hour: number;
431 /** Day of the week for `week`, 0 (Sunday) to 6. */
432 weekday: number;
433};
434
435/**
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened436 * Things that happen in the workspace a routine can run on
437 * (docs/WORKSPACE.md, "Routines"). Each run is one session about the one
438 * thing that happened, in a repository its sponsor can read.
439 */
440export const ROUTINE_EVENTS = [
441 { key: "pull_ready", label: "A pull request is ready for review", hint: "Opened ready, or moved out of draft." },
442 { key: "pull_merged", label: "A pull request is merged", hint: "On any branch it targets." },
443 { key: "checks_failed", label: "Checks fail on a pull request", hint: "Its required checks failed or errored." },
444 { key: "issue_opened", label: "An issue is opened", hint: "By a person or an agent." },
445 { key: "deploy_failed", label: "A deploy fails", hint: "A production or preview deploy." },
446] as const;
447
448export type RoutineEvent = (typeof ROUTINE_EVENTS)[number]["key"];
449
450/**
451 * A routine: work an agent does on a schedule or when something happens, such as Izzy's Monday digest
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 asked452 * of support themes. Each run is a session posted in the routine's channel,
453 * paid from the agent's budget, and run with the access of the person who
454 * set it up (its sponsor), never more.
455 */
456export type AgentRoutine = {
457 id: string;
458 agent_id: string;
459 name: string;
460 instructions: string;
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened461 /** When it runs on a clock; null when it runs only on events. */
462 schedule: RoutineSchedule | null;
463 /** What it runs on; empty when it runs only on its schedule. */
464 events: RoutineEvent[];
465 /** Which repositories its events come from, by `workspace/name`; empty: every one its sponsor can read. */
466 repos: 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 asked467 channel_id: string;
468 channel_name: string | null;
469 sponsor: string;
470 sponsor_username: string | null;
471 enabled: boolean;
472 /** Why g1t paused it, when it did. */
473 paused_note: string | null;
474 next_run_at: string | null;
475 last_run_at: string | null;
476 last_session_id: string | null;
477 runs: number;
478 created_at: string;
479 updated_at: string;
480};
481
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened482/** A routine suggested from an agent's responsibilities, for an owner to add in one step. */
483export type RoutineSuggestion = { responsibility: string; routine: Omit<NewRoutine, "channel_id"> };
484
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 asked485export type NewRoutine = {
486 name: string;
487 instructions: string;
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened488 /** A schedule, events, or both; at least one. */
489 schedule: RoutineSchedule | null;
490 events?: RoutineEvent[];
491 repos?: 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 asked492 /** A channel the agent is in, by id. */
493 channel_id: string;
494 enabled?: boolean;
495};
496
497/**
498 * The workspace's say over all its agents together, set by owners: one
499 * monthly budget across every agent, the budget a new agent starts with,
500 * and the cap a session starts with. The workspace's spend limit and AI
501 * credit (billing) sit above all of it.
502 */
503export type AgentPolicy = {
504 /** Every agent's spend together in a month. Null: only the workspace's spend limit. */
505 monthly_micros: number | null;
506 /** The monthly budget a new agent gets. Null: none. */
507 default_agent_monthly_micros: number | null;
508 /** The cap one session starts with, unless its agent's per-task cap is lower. */
509 default_session_micros: number;
510};
511
512export type SpendSlice = { key: string; label: string; micros: number; count: number };
513
514/** Where an agent's (or every agent's) month went. */
515export type AgentSpendBreakdown = {
516 period: string;
517 total_micros: number;
518 /** Chat replies, sessions, routines, helping colleagues. */
519 by_kind: SpendSlice[];
520 by_model: SpendSlice[];
521 /** Who asked: the work done for each person. */
522 by_person: SpendSlice[];
523 by_agent: SpendSlice[];
524 by_team: SpendSlice[];
525 /** The costliest sessions this month. */
526 top_sessions: AgentSession[];
527 /** Spend by day this month. */
528 days: { day: string; micros: number }[];
529};
530
531/** Agents mode's front page. */
532export type AgentsOverview = {
533 policy: AgentPolicy;
534 /** Every agent's spend this month, against the policy's budget. */
535 spent_month_micros: number;
536 /** The highest alert this month: 75, 90 or 100 (% of the workspace's agent budget). */
537 alert: number | null;
538 agents: WorkspaceAgent[];
539 /** Live sessions, counted by agent id, for the roster. */
540 live_by_agent: Record<string, number>;
541 /** Sessions live now that the viewer can see. */
542 live: AgentSession[];
543 /** Sessions waiting on the viewer: spend they may approve. */
544 waiting_on_you: AgentSession[];
545 /** Recently finished sessions the viewer can see. */
546 recent: AgentSession[];
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened547 /** The next routines to run on a schedule. */
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 asked548 upcoming: (AgentRoutine & { agent_handle: string; agent_name: string })[];
549 spend: AgentSpendBreakdown;
550 can_manage: boolean;
551};
552
553/** One thing an agent did, for its Activity tab. */
554export type AgentActivity = {
555 id: string;
556 kind: "reply" | "session";
557 status: string;
558 channel_id: string;
559 channel_name: string | null;
560 /** The session's title; null for a reply or one the viewer can't see. */
561 title: string | null;
562 asked_by_username: string | null;
563 model: string | null;
564 tools: number;
565 charged_micros: number;
566 created_at: string;
567 visible: boolean;
568 /** For a reply, the message it posted; for a session, its id. */
569 ref: string | null;
570};
571
572export type AgentVersion = { version: number; changed_by: string; created_at: string; definition: Partial<NewWorkspaceAgent> };
573
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar574/** A card action, as chat hands it to agents. */
575export type AgentCardAction = {
576 workspace: string;
577 channel_id: string;
578 message_id: string;
579 viewer: User;
580 card: { kind: string; ref: string | null };
581 action_id: string;
582 input: string | null;
583};
584
Chat and workspace agents: channels, DMs and named agents you talk to585export type WorkspaceAgentsApi = {
586 list(workspace: string, viewer: User): Promise<Result<WorkspaceAgent[]>>;
587 get(workspace: string, handle: string, viewer: User): Promise<Result<WorkspaceAgent>>;
588 /** Internal: by id, for the chat service resolving members. */
589 byIds(ids: string[]): Promise<WorkspaceAgent[]>;
590 create(workspace: string, viewer: User, input: NewWorkspaceAgent): Promise<Result<WorkspaceAgent>>;
591 update(
592 workspace: string,
593 handle: string,
594 viewer: User,
595 changes: Partial<NewWorkspaceAgent>,
596 ): Promise<Result<WorkspaceAgent>>;
597 archive(workspace: string, handle: string, viewer: User): Promise<Result<null>>;
598 templates(): Promise<AgentTemplate[]>;
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)599 /**
600 * Internal: the workspace's built-in `@g1t` agent, made if it does not
601 * exist yet. The chat service asks for it when someone mentions @g1t in
602 * a channel it is not in yet.
603 */
604 builtin(workspace: string, workspaceId: string): Promise<Result<WorkspaceAgent>>;
Chat and workspace agents: channels, DMs and named agents you talk to605 /** The chat service hands over a message for an agent to answer. Returns at once. */
606 deliver(delivery: AgentDelivery): Promise<Result<null>>;
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 asked607 overview(workspace: string, viewer: User): Promise<Result<AgentsOverview>>;
608 sessions(
609 workspace: string,
610 viewer: User,
611 filter?: { handle?: string | null; status?: "live" | "done" | null; limit?: number | null },
612 ): Promise<Result<AgentSession[]>>;
613 session(workspace: string, id: string, viewer: User): Promise<Result<AgentSessionDetail>>;
614 /** Stops a session and every session under it. */
615 stopSession(workspace: string, id: string, viewer: User): Promise<Result<AgentSession>>;
616 /** Raises a stopped session's cap and lets it go on. Owners only. */
617 approveSession(workspace: string, id: string, viewer: User, capMicros: number): Promise<Result<AgentSession>>;
618 /** A person's message to a session, running or finished: it reads it and goes on. */
619 steerSession(workspace: string, id: string, viewer: User, body: string): Promise<Result<AgentSession>>;
620 memories(workspace: string, handle: string, viewer: User): Promise<Result<AgentMemory[]>>;
621 remember(
622 workspace: string,
623 handle: string,
624 viewer: User,
625 input: { body: string; scope: AgentMemoryScope; scope_ref?: string | null },
626 ): Promise<Result<AgentMemory>>;
627 updateMemory(
628 workspace: string,
629 handle: string,
630 viewer: User,
631 id: string,
632 changes: { body?: string; pinned?: boolean },
633 ): Promise<Result<AgentMemory>>;
634 forget(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
Routines run when something happens: a pull request ready for review or merged, checks or a deploy failing, an issue opened635 routines(workspace: string, handle: string, viewer: User): Promise<Result<{ routines: AgentRoutine[]; suggestions: RoutineSuggestion[] }>>;
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 asked636 saveRoutine(workspace: string, handle: string, viewer: User, input: NewRoutine, id?: string | null): Promise<Result<AgentRoutine>>;
637 deleteRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<null>>;
638 /** Runs a routine now, as a session. */
639 runRoutine(workspace: string, handle: string, viewer: User, id: string): Promise<Result<AgentSession>>;
640 /** Where the month went: one agent's, or every agent's. */
641 spend(workspace: string, viewer: User, handle?: string | null): Promise<Result<AgentSpendBreakdown>>;
642 activity(workspace: string, handle: string, viewer: User): Promise<Result<AgentActivity[]>>;
643 versions(workspace: string, handle: string, viewer: User): Promise<Result<AgentVersion[]>>;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar644 /**
645 * Internal, from chat: a person pressed an action on one of agents'
646 * cards. Agents checks they may, acts, and updates the card.
647 */
648 cardAction(input: AgentCardAction): Promise<Result<CardActionResult>>;
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 asked649 policy(workspace: string, viewer: User): Promise<Result<AgentPolicy>>;
650 setPolicy(workspace: string, viewer: User, policy: Partial<AgentPolicy>): Promise<Result<AgentPolicy>>;
Chat and workspace agents: channels, DMs and named agents you talk to651};
652
653async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
654 const response = await service.fetch(`https://service/rpc/${method}`, {
655 method: "POST",
656 headers: { "content-type": "application/json" },
657 body: JSON.stringify(args),
658 });
659 if (!response.ok) {
660 throw new Error(`${method} failed with status ${response.status}`);
661 }
662 return (await response.json()) as T;
663}
664
665export function workspaceAgentsClient(service: ServiceBinding): WorkspaceAgentsApi {
666 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
667 return {
668 list: (workspace, viewer) => call("list", { workspace, viewer }),
669 get: (workspace, handle, viewer) => call("get", { workspace, handle, viewer }),
670 byIds: (ids) => call("by_ids", { ids }),
671 create: (workspace, viewer, input) => call("create", { workspace, viewer, input }),
672 update: (workspace, handle, viewer, changes) => call("update", { workspace, handle, viewer, changes }),
673 archive: (workspace, handle, viewer) => call("archive", { workspace, handle, viewer }),
674 templates: () => call("templates", {}),
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)675 builtin: (workspace, workspaceId) => call("builtin", { workspace, workspace_id: workspaceId }),
Chat and workspace agents: channels, DMs and named agents you talk to676 deliver: (delivery) => call("deliver", delivery),
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 asked677 overview: (workspace, viewer) => call("overview", { workspace, viewer }),
678 sessions: (workspace, viewer, filter) => call("sessions", { workspace, viewer, ...(filter ?? {}) }),
679 session: (workspace, id, viewer) => call("session", { workspace, id, viewer }),
680 stopSession: (workspace, id, viewer) => call("stop_session", { workspace, id, viewer }),
681 approveSession: (workspace, id, viewer, capMicros) => call("approve_session", { workspace, id, viewer, cap_micros: capMicros }),
682 steerSession: (workspace, id, viewer, body) => call("steer_session", { workspace, id, viewer, body }),
683 memories: (workspace, handle, viewer) => call("memories", { workspace, handle, viewer }),
684 remember: (workspace, handle, viewer, input) => call("remember", { workspace, handle, viewer, input }),
685 updateMemory: (workspace, handle, viewer, id, changes) => call("update_memory", { workspace, handle, viewer, id, changes }),
686 forget: (workspace, handle, viewer, id) => call("forget", { workspace, handle, viewer, id }),
687 routines: (workspace, handle, viewer) => call("routines", { workspace, handle, viewer }),
688 saveRoutine: (workspace, handle, viewer, input, id) => call("save_routine", { workspace, handle, viewer, input, id: id ?? null }),
689 deleteRoutine: (workspace, handle, viewer, id) => call("delete_routine", { workspace, handle, viewer, id }),
690 runRoutine: (workspace, handle, viewer, id) => call("run_routine", { workspace, handle, viewer, id }),
691 spend: (workspace, viewer, handle) => call("spend", { workspace, viewer, handle: handle ?? null }),
692 activity: (workspace, handle, viewer) => call("activity", { workspace, handle, viewer }),
693 versions: (workspace, handle, viewer) => call("versions", { workspace, handle, viewer }),
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar694 cardAction: (input) => call("card_action", input),
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 asked695 policy: (workspace, viewer) => call("policy", { workspace, viewer }),
696 setPolicy: (workspace, viewer, policy) => call("set_policy", { workspace, viewer, policy }),
Chat and workspace agents: channels, DMs and named agents you talk to697 };
698}

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