Skip to content
88 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 { changeRows, integrationHint, lineDiff, readDefinition } from "./agent-builder.ts";
5
6test("a draft comes back from the page checked for shape", () => {
7 const read = readDefinition(
8 JSON.stringify({
9 handle: "juniper",
10 display_name: " Juniper ",
11 title: "Release Manager",
12 instructions: "Cut releases.",
13 responsibilities: ["One", 2, "Two"],
14 personality_preset: "loud",
15 routing: { floor: "huge", ceiling: "small", providers: ["x"], pinned: "a/b" },
16 budget: { monthly_micros: 20_000_000, task_micros: -1 },
17 skills_off: ["data"],
18 team: "ops",
19 scope: "personal",
20 }),
21 );
22 assert.ok(read);
23 assert.equal(read.display_name, "Juniper");
24 assert.deepEqual(read.responsibilities, ["One", "Two"]);
25 assert.equal(read.personality_preset, "crisp");
26 assert.deepEqual(read.routing, { floor: null, ceiling: "small", providers: [], pinned: null }, "nothing but limits");
27 assert.deepEqual(read.budget, { monthly_micros: 20_000_000, daily_micros: null, task_micros: null });
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 assert.ok(!("team" in read) && !("department" in read), "a draft names no team: teams are added as memberships");
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 assert.equal(read.scope, "personal");
30 assert.equal(readDefinition("not json"), null);
31 assert.equal(readDefinition("[1]"), null);
32});
33
34test("an integration it needs: owners connect it, anyone else asks, and soon is soon", () => {
35 const owner = integrationHint("acme", { id: "linear", why: "Reads issues." }, true);
36 assert.equal(owner?.action, "connect");
37 assert.equal(owner?.href, "/acme/-/integrations/trackers?add=linear#add");
38 const member = integrationHint("acme", { id: "linear", why: "Reads issues." }, false);
39 assert.deepEqual(member && { action: member.action, href: member.href }, { action: "ask", href: "/acme/-/marketplace/integrations/linear" });
40 assert.equal(integrationHint("acme", { id: "box", why: "" }, true)?.action, "soon");
41 assert.equal(integrationHint("acme", { id: "nope", why: "" }, true), null);
42});
43
44test("a job's change reads as lines kept, removed and added", () => {
45 assert.deepEqual(lineDiff("a\nb\nc", "a\nB\nc\nd"), [
46 { kind: "same", text: "a" },
47 { kind: "removed", text: "b" },
48 { kind: "added", text: "B" },
49 { kind: "same", text: "c" },
50 { kind: "added", text: "d" },
51 ]);
52 assert.deepEqual(lineDiff("same", "same"), [{ kind: "same", text: "same" }]);
53});
54
55test("a drafted change lists each field it touches, before and after", () => {
56 const agent = {
57 handle: "juniper",
58 display_name: "Juniper",
59 title: "Release Manager",
60 instructions: "Cut releases.",
61 responsibilities: ["One", "Two"],
62 personality_preset: "crisp" as const,
63 personality: "",
64 skills_off: [],
65 routing: { floor: null, ceiling: null, providers: [], pinned: null },
66 budget: { monthly_micros: 20_000_000, daily_micros: null, task_micros: 2_000_000 },
67 };
68 const rows = changeRows(agent as never, {
69 display_name: "Wren",
70 handle: "wren",
71 instructions: "Cut releases.\nAnswer in Spanish.",
72 skills_off: ["data"],
73 routing: { ceiling: "small" },
74 budget: { monthly_micros: 50_000_000 },
75 });
76 assert.deepEqual(
77 rows.map((r) => [r.label, r.before, r.after]),
78 [
79 ["Name", "Juniper", "Wren"],
80 ["Handle", "@juniper", "@wren"],
81 ["Job", "Cut releases.", "Cut releases.\nAnswer in Spanish."],
82 ["Skills", "All on", "Turned off: data"],
83 ["Model ceiling", "None", "Fast"],
84 ["Monthly budget", "$20", "$50"],
85 ],
86 );
87 assert.equal(rows[2]!.lines?.filter((l) => l.kind === "added").length, 1);
88});