Skip to content
165 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.

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.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { FOUNDATIONAL_SKILL_IDS } from "../../../packages/contracts/src/skills.ts";
5
6import {
7 BUILDER_HANDLE,
8 draftSystem,
9 freeHandle,
10 handleFrom,
11 jsonIn,
12 proposalFrom,
13 redraftFrom,
14 startingBudget,
15 trySystem,
16 tryTurns,
17 wordsOf,
18} from "./builder.ts";
19import { applyChanges } from "./definition.ts";
20import { checkHandle } from "./handle.ts";
21import { TEMPLATE_IDS } from "./templates.ts";
22
23const drafted = {
24 display_name: "Juniper",
25 name_ideas: ["Wren", "Juniper", "Basil", "!!"],
26 title: "Release Manager",
27 department: "Engineering",
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.28 team: "release",
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.29 instructions: "You cut releases.\n- Read merged pull requests since the last tag.\n- Write the notes.",
30 responsibilities: ["Cut a release every Tuesday", "Write the release notes", "Ask in #releases before tagging"],
31 personality_preset: "friendly",
32 personality: "Short sentences.",
33 skills: ["documents", "code", "made-up"],
34 integrations: [{ id: "linear", why: "Reads the issues a release closes." }, { id: "nope", why: "x" }, { id: "linear", why: "twice" }],
35 routines: [{ name: "Tuesday release", when: "Every Tuesday at 10:00", instructions: "Cut the release." }, { name: "", when: "" }],
36 floor: "frontier",
37 ceiling: "small",
38};
39
40test("a description becomes a complete definition that create takes as it is", () => {
41 const made = proposalFrom(drafted, { scope: "personal", taken: new Set(), budget: startingBudget("personal", null) });
42 assert.ok(made.ok);
43 const { definition } = made.value;
44 assert.equal(definition.handle, "juniper");
45 assert.equal(definition.display_name, "Juniper");
46 assert.equal(definition.scope, "personal");
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 assert.ok(!("team" in definition) && !("department" in definition), "a draft names no team or department, whatever the model says");
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.48 assert.deepEqual(definition.budget, { monthly_micros: 20_000_000, daily_micros: null, task_micros: 2_000_000 }, "$20 a month, $2 a session");
49 assert.deepEqual(definition.routing, { floor: null, ceiling: "small", providers: [], pinned: null }, "a floor above the ceiling is dropped");
50 assert.deepEqual(made.value.skills, ["documents", "code"], "unknown skills are left out");
51 assert.deepEqual(definition.skills_off, FOUNDATIONAL_SKILL_IDS.filter((id) => id !== "documents" && id !== "code"));
52 assert.deepEqual(made.value.integrations, [{ id: "linear", why: "Reads the issues a release closes." }], "only the catalog's, once each");
53 assert.equal(made.value.routines.length, 1);
54 assert.deepEqual(made.value.name_ideas, ["Wren", "Basil"]);
55 const { scope: _scope, ...input } = definition;
56 assert.ok(applyChanges(null, input, TEMPLATE_IDS).ok);
57});
58
59test("a draft takes a free handle, and a workspace draft the workspace's default budget", () => {
60 const made = proposalFrom(drafted, { scope: "workspace", taken: new Set(["juniper", "juniper-2"]), budget: startingBudget("workspace", 30_000_000) });
61 assert.ok(made.ok);
62 assert.equal(made.value.definition.handle, "juniper-3");
63 assert.deepEqual(made.value.definition.budget, { monthly_micros: 30_000_000, daily_micros: null, task_micros: null });
64});
65
66test("a draft with every skill unnamed keeps them all on; one with no job can't be created", () => {
67 const all = proposalFrom({ ...drafted, skills: [] }, { scope: "workspace", taken: new Set(), budget: startingBudget("workspace", null) });
68 assert.ok(all.ok);
69 assert.deepEqual(all.value.definition.skills_off, []);
70 assert.equal(proposalFrom({ ...drafted, instructions: "" }, { scope: "workspace", taken: new Set(), budget: startingBudget("workspace", null) }).ok, false);
71 assert.equal(proposalFrom(null, { scope: "workspace", taken: new Set(), budget: startingBudget("workspace", null) }).ok, false);
72});
73
74test("the model's answer is read fenced or bare, and junk is nothing", () => {
75 assert.deepEqual(jsonIn('```json\n{"a": 1}\n```'), { a: 1 });
76 assert.deepEqual(jsonIn('Here it is: {"a": {"b": 2}} done'), { a: { b: 2 } });
77 assert.equal(jsonIn("no json"), null);
78 assert.equal(jsonIn("[1, 2]"), null);
79});
80
81test("handles come from names, and are never reserved", () => {
82 assert.equal(handleFrom("Margo Lee"), "margo-lee");
83 assert.equal(handleFrom("Zoë"), "zoe");
84 assert.ok(checkHandle(freeHandle("g1t", new Set())).ok);
85 assert.ok(checkHandle(freeHandle("New", new Set())).ok);
86 assert.equal(checkHandle(BUILDER_HANDLE).ok, false, "the builder's bill name is no agent's handle");
87});
88
89const before = (() => {
90 const made = applyChanges(
91 null,
92 {
93 handle: "juniper",
94 display_name: "Juniper",
95 title: "Release Manager",
96 instructions: "You cut releases.",
97 responsibilities: ["Cut releases", "Write notes"],
98 budget: { monthly_micros: 20_000_000, task_micros: 2_000_000 },
99 },
100 TEMPLATE_IDS,
101 );
102 assert.ok(made.ok);
103 return made.value;
104})();
105
106test("a change in words keeps only what differs", () => {
107 const changed = redraftFrom(
108 { display_name: "Juniper", instructions: "You cut releases. Answer in Spanish.", monthly_dollars: 50, session_dollars: 2, summary: "Answers in Spanish; $50 a month." },
109 before,
110 false,
111 );
112 assert.ok(changed.ok);
113 assert.deepEqual(changed.value.changes, { instructions: "You cut releases. Answer in Spanish.", budget: { monthly_micros: 50_000_000 } });
114 assert.equal(changed.value.summary, "Answers in Spanish; $50 a month.");
115});
116
117test("renaming moves a handle that followed the name, and @g1t keeps who it is", () => {
118 const renamed = redraftFrom({ display_name: "Wren", summary: "Renamed." }, before, false);
119 assert.ok(renamed.ok);
120 assert.deepEqual(renamed.value.changes, { display_name: "Wren", handle: "wren" });
121 const builtin = redraftFrom({ display_name: "Boss", title: "Chief", personality: "Dry." }, before, true);
122 assert.ok(builtin.ok);
123 assert.deepEqual(builtin.value.changes, { personality: "Dry." });
124});
125
126test("a change that asks for nothing says so", () => {
127 const none = redraftFrom({ summary: "" }, before, false);
128 assert.equal(none.ok, false);
129 assert.equal(redraftFrom({ instructions: "" }, before, false).ok, false, "a job is never emptied");
130});
131
132test("Try it takes alternating turns that end with the person's", () => {
133 const turns = tryTurns([
134 { role: "assistant", content: "hi" },
135 { role: "user", content: "one" },
136 { role: "user", content: "two" },
137 { role: "assistant", content: "ok" },
138 { role: "user", content: "three" },
139 ]);
140 assert.ok(turns.ok);
141 assert.deepEqual(turns.value, [
142 { role: "user", content: "one\n\ntwo" },
143 { role: "assistant", content: "ok" },
144 { role: "user", content: "three" },
145 ]);
146 assert.equal(tryTurns([{ role: "user", content: "a" }, { role: "assistant", content: "b" }]).ok, false);
147 assert.equal(tryTurns([]).ok, false);
148 assert.equal(tryTurns(Array.from({ length: 30 }, () => ({ role: "user", content: "x" }))).ok, false);
149});
150
151test("Try it speaks with the agent's own prompt, said to be a preview with no tools", () => {
152 const prompt = trySystem({ workspace: "acme", definition: before, asker: { username: "ana" }, today: new Date("2026-10-10T00:00:00Z") });
153 assert.match(prompt, /You are Juniper \(@juniper\)/);
154 assert.match(prompt, /This is a preview/);
155 assert.match(prompt, /You can only read this conversation right now/);
156 assert.match(draftSystem("acme", "personal"), /personal agent/);
157 assert.match(draftSystem("acme", "workspace"), /linear: Linear/);
158});
159
160test("descriptions and requests are checked", () => {
161 assert.equal(wordsOf("", "description").ok, false);
162 assert.equal(wordsOf("x".repeat(2001), "description").ok, false);
163 assert.equal(wordsOf("x".repeat(1001), "request").ok, false);
164 assert.deepEqual(wordsOf(" Cut releases ", "description"), { ok: true, value: "Cut releases" });
165});