Skip to content
168 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 * `@g1t`, every workspace's built-in orchestrator (docs/WORKSPACE.md, "g1t,
3 * the orchestrator"). Pure, so it is tested on its own.
4 *
5 * It is an agent row like any other, marked builtin, so it is billed,
6 * gated, routed and audited the same way. What sets it apart is here:
7 * the fields nobody may change, the job it always has, the roster of
8 * specialists it is given, and the rails on how it delegates.
9 */
10import type { AgentStatus, ModelTier, NewWorkspaceAgent } from "@g1t/contracts";
11
12import { BUILTIN_AGENT_HANDLE, ORCHESTRATOR_TEMPLATE } from "../../../packages/contracts/src/workspace-agents.ts";
13import { type Checked, type Definition, DEFAULT_AUTONOMY, DEFAULT_BUDGET, DEFAULT_CAPACITY, DEFAULT_ROUTING } from "./definition.ts";
Merge branch 'worktree-agent-a1398e81ad1a64c5f'14import { MAX_HAND_OFFS } from "./tools.ts";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)15
16export const BUILTIN_ROLE = "Your orchestrator: delegates to the team's agents, or does the work itself";
17
18/** What a new workspace's @g1t starts as. `instructions` are the workspace's additions, none at first. */
19export function builtinDefinition(): Definition {
20 return {
21 handle: BUILTIN_AGENT_HANDLE,
22 display_name: "g1t",
23 role: BUILTIN_ROLE,
24 instructions: "",
25 personality_preset: "friendly",
26 personality: "",
27 routing: DEFAULT_ROUTING,
28 budget: DEFAULT_BUDGET,
29 autonomy: DEFAULT_AUTONOMY,
30 capacity: DEFAULT_CAPACITY,
31 template: ORCHESTRATOR_TEMPLATE,
32 avatar_seed: BUILTIN_AGENT_HANDLE,
33 title: "Orchestrator",
34 team: null,
35 department: "",
36 responsibilities: [],
37 subagents: [],
38 faces: "internal",
Agents recall what Docs say before they answer or work, and each has required reading39 reading: [],
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)40 };
41}
42
43/** What nobody may change on the built-in agent: who it is and what its job is. */
44export const PROTECTED: (keyof NewWorkspaceAgent)[] = [
45 "handle",
46 "display_name",
47 "role",
48 "title",
49 "team",
50 "department",
51 "responsibilities",
52 "subagents",
53 "template",
54 "faces",
55];
56
57/**
58 * `changes` to the built-in agent with its fixed fields taken out, whatever
59 * they say: a profile form sends every field (an empty title, no
60 * responsibilities), and only the editable ones apply.
61 */
62export function builtinChanges(_current: Definition, changes: Partial<NewWorkspaceAgent>): Checked<Partial<NewWorkspaceAgent>> {
63 if (!changes || typeof changes !== "object") return { ok: false, message: "Send the agent's fields." };
64 const rest: Partial<NewWorkspaceAgent> = { ...changes };
65 for (const key of PROTECTED) delete rest[key];
66 return { ok: true, value: rest };
67}
68
69/** One specialist as @g1t sees it. */
70export type Specialist = {
71 handle: string;
72 display_name: string;
73 role: string;
74 title?: string;
75 team?: string | null;
76 department?: string;
77 responsibilities?: string[];
78 status: AgentStatus;
79 spent_month_micros: number;
80 /** The monthly cap, or null for none. */
81 monthly_micros: number | null;
82};
83
84const STATUS_WORDS: Record<AgentStatus, string> = {
85 idle: "idle",
86 working: "working",
87 waiting: "waiting on someone",
88 out_of_budget: "out of budget",
89 paused: "paused",
90};
91
92const dollars = (micros: number) => `$${(Math.max(0, micros) / 1_000_000).toFixed(2)}`;
93
94/** Where a specialist sits: "QA Engineer on the qa team", "QA Engineer, QA", or its role. */
95function placeLine(agent: Specialist): string {
96 const title = agent.title?.trim();
97 if (!title) return agent.role.replace(/\.$/, "");
98 if (agent.team) return `${title} on the ${agent.team} team`;
99 return agent.department?.trim() ? `${title}, ${agent.department.trim()}` : title;
100}
101
102/**
103 * The roster, one line per specialist, so g1t can route "QA should look"
104 * to the right one: `- @margo (Margo): QA Engineer, QA. Does: review pull
105 * requests; write test plans. idle; $1.20 of $20.00 this month.`
106 */
107export function rosterLines(specialists: Specialist[]): string {
108 if (!specialists.length) return "There are no specialists in this workspace yet.";
109 return specialists
110 .map((agent) => {
111 const name = agent.display_name && agent.display_name.toLowerCase() !== agent.handle ? ` (${agent.display_name})` : "";
112 const spend = agent.monthly_micros ? `${dollars(agent.spent_month_micros)} of ${dollars(agent.monthly_micros)}` : `${dollars(agent.spent_month_micros)}, no cap`;
113 const duties = agent.responsibilities?.length ? ` Does: ${agent.responsibilities.map((d) => d.replace(/\.$/, "")).join("; ")}.` : "";
114 return `- @${agent.handle}${name}: ${placeLine(agent)}.${duties} ${STATUS_WORDS[agent.status] ?? agent.status}; ${spend} this month.`;
115 })
116 .join("\n");
117}
118
Merge branch 'worktree-agent-a1398e81ad1a64c5f'119/** The most colleagues one reply may hand work to (the hand_off tool's rail). */
120export const MAX_DELEGATES = MAX_HAND_OFFS;
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)121
122/** @g1t's job: fixed, whatever the workspace adds. */
123export function orchestratorInstructions(specialists: Specialist[], extra: string): string {
124 const available = specialists.filter((agent) => agent.status !== "out_of_budget" && agent.status !== "paused");
125 const job = [
126 "You are the workspace's orchestrator. People come to you when they don't know who should do something. You either answer, hand the work to the right specialist, or do it yourself.",
127 "",
128 "### The team",
129 "",
130 rosterLines(specialists),
131 "",
132 "### How you decide",
133 "",
134 "1. **Answer directly** when it is a question, a summary or a quick judgment you can give from this conversation.",
Merge branch 'worktree-agent-a1398e81ad1a64c5f'135 "2. **Delegate** when a specialist's role fits the work: call hand_off with their handle and a crisp brief, written to them: what is wanted, why, what done looks like, and any constraint (deadlines, what not to touch). If they are in this conversation the brief is posted here; otherwise a group message opens with the person who asked, you and them, and a card here links to it. Then tell the person in a sentence who has it and where.",
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)136 "3. **Do it yourself, or suggest a specialist,** when nobody fits. Say so plainly, offer to take it on yourself, and if this kind of work will recur, suggest setting one up (for example: \"Want me to set up a release manager for this?\").",
137 "",
138 "### Rules for delegating",
139 "",
Merge branch 'worktree-agent-a1398e81ad1a64c5f'140 "- Only hand_off delegates. An @mention in your message wakes no agent and reaches nobody outside this conversation, so writing \"@mike, could you…\" hands nothing over.",
141 "- Describing the team is not delegating: name specialists without @ (\"Mike, our technical recruiter\") unless they are in this conversation.",
142 `- Hand off to at most ${MAX_DELEGATES} specialists for one message. Split bigger work into steps and hand off the next step when the first is done.`,
143 "- Never delegate in a loop: don't hand work back to a specialist who handed it to you, and don't hand the same work to the same specialist twice.",
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)144 "- Don't delegate to a specialist who is out of budget or paused; say they are unavailable and why.",
Merge branch 'worktree-agent-a1398e81ad1a64c5f'145 "- The person sees every hand-off, and their access still limits what any agent does for them.",
146 "- Once you've handed something off, let the specialist answer; speak again when someone asks you.",
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)147 "- When asked what everyone is working on, answer from the team list above.",
148 available.length ? "" : "\nNo specialist is available right now, so do the work yourself or suggest creating one.",
149 ].join("\n");
150 const added = extra.trim();
151 return added ? `${job}\n\n### Added by this workspace\n\n${added}` : job;
152}
153
154/** How long a thread is before @g1t's delegation call is worth a larger model. */
155export const LONG_THREAD = 6;
156
157/**
158 * Where @g1t's reply starts: the small tier, or the large one for a
159 * delegation decision in a long thread (more than `LONG_THREAD` messages,
160 * with specialists to choose from). Its floor and ceiling apply after.
161 */
162export function orchestratorTier(messages: number, specialists: number): ModelTier {
163 return messages > LONG_THREAD && specialists > 0 ? "large" : "small";
164}
165
166/** The friendly notice when @g1t has no model to run on. */
167export const BUILTIN_NO_MODEL =
168 "Hi, I'm g1t. I'd love to help, but this workspace doesn't have a model I can use yet. An owner can add AI credit under Billing, or connect the workspace's own model provider under Integrations, and I'll be ready.";

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