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