Tell every bot its own id, in every run
The bot's id never reached its prompt: `composeBotPrompt` only took its instructions, the appendix, its memory and its workspace, and `grabBotPrompt` never passed `bot.id` or `bot.name` on. A bot could only learn its id by accident, from a tool description that named it, so "which bot am I" was unanswerable in most contexts. - Open the prompt with who the bot is (`composeIdentity`): its name, whether it's the coordinator or a worker, its role, and its id, said to be how tools and refs point back at it. `grabBotPrompt` now passes the bot, which covers chat replies, tasks, routines and delegated tasks, since both runners build their prompt there. - Turn the notes' example refs into functions bound to the running bot: `chatReplyNote`, `botFilesNote` and `coordinatorReplyNote` take its id, so the folder-ref example no longer points at a hardcoded `owner_id: 5` (another bot's folder) nor `[bot:3]`. ComputerFilesNote keeps the computer it's given. - Stop `create_task` from hiding the worker's own id in its own chat. Tests: four new `composeBotPrompt` cases pin the identity block (worker, coordinator, unnamed, no bot at all), and a new `harness-prompts.test.ts` checks each note's own-folder and self refs point at exactly the running bot, for three ids.
This commit is contained in:
@@ -2,9 +2,47 @@ import { describe, expect, test } from "bun:test";
|
||||
import composeBotPrompt from "./compose-bot-prompt";
|
||||
|
||||
describe("composeBotPrompt", () => {
|
||||
test("opens with who the bot is, its id included", () => {
|
||||
expect(composeBotPrompt({ bot: { id: 7, name: "Scout", kind: "worker", role: "Finds things" } })).toBe(
|
||||
[
|
||||
"# You are Scout",
|
||||
"You are a worker bot on this crew. Your role: Finds things.",
|
||||
"Your bot id is 7. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref `[bot:7]`, never another bot's.",
|
||||
].join("\n\n"),
|
||||
);
|
||||
});
|
||||
|
||||
test("knows the coordinator by its kind and keeps an unknown name", () => {
|
||||
expect(composeBotPrompt({ bot: { id: "1", kind: "coordinator" } })).toBe(
|
||||
[
|
||||
"# You are A bot",
|
||||
"You are the coordinator on this crew.",
|
||||
"Your bot id is 1. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref `[bot:1]`, never another bot's.",
|
||||
].join("\n\n"),
|
||||
);
|
||||
});
|
||||
|
||||
test("says nothing about identity when the caller gives no bot", () => {
|
||||
expect(composeBotPrompt({ bot: { id: null, name: "Scout" }, system_prompt: "Core." })).toBe(
|
||||
"Core.",
|
||||
);
|
||||
});
|
||||
|
||||
test("puts the identity above the core instructions", () => {
|
||||
expect(composeBotPrompt({ bot: { id: 4, name: "Scout" }, system_prompt: "Core." })).toBe(
|
||||
[
|
||||
"# You are Scout",
|
||||
"You are a worker bot on this crew.",
|
||||
"Your bot id is 4. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref `[bot:4]`, never another bot's.",
|
||||
"Core.",
|
||||
].join("\n\n"),
|
||||
);
|
||||
});
|
||||
|
||||
test("orders core instructions, the workspace, then memory", () => {
|
||||
expect(
|
||||
composeBotPrompt({
|
||||
bot: { id: 7, name: "Scout" },
|
||||
system_prompt: "# You are Scout\n\nFind things.",
|
||||
memory: "- Cite sources.",
|
||||
user_memory: "- The user prefers short replies.",
|
||||
@@ -20,6 +58,9 @@ describe("composeBotPrompt", () => {
|
||||
}),
|
||||
).toBe(
|
||||
[
|
||||
"# You are Scout",
|
||||
"You are a worker bot on this crew.",
|
||||
"Your bot id is 7. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref `[bot:7]`, never another bot's.",
|
||||
"# You are Scout\n\nFind things.",
|
||||
`# Workspace: Launch\n\nYou're working in the workspace "Launch". Follow its context and your instructions for it below, over your general habits where they differ.\n\nThe workspace's context, which the user sets:\n\nThe spring launch.\n\nShip by Friday.`,
|
||||
"# Your instructions in Launch\n\nOwn the changelog.",
|
||||
@@ -33,6 +74,7 @@ describe("composeBotPrompt", () => {
|
||||
test("puts the install-wide appendix right after the core instructions", () => {
|
||||
expect(
|
||||
composeBotPrompt({
|
||||
bot: { id: 4, name: "Scout" },
|
||||
system_prompt: "Core.",
|
||||
appendix: "Never invent sources.",
|
||||
workspace: { name: "Launch", description: "The spring launch." },
|
||||
@@ -40,6 +82,9 @@ describe("composeBotPrompt", () => {
|
||||
}),
|
||||
).toBe(
|
||||
[
|
||||
"# You are Scout",
|
||||
"You are a worker bot on this crew.",
|
||||
"Your bot id is 4. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref `[bot:4]`, never another bot's.",
|
||||
"Core.",
|
||||
"# Install-wide instructions\n\nNever invent sources.",
|
||||
`# Workspace: Launch\n\nYou're working in the workspace "Launch". Follow its context and your instructions for it below, over your general habits where they differ.\n\nThe workspace's context, which the user sets:\n\nThe spring launch.`,
|
||||
|
||||
@@ -9,7 +9,27 @@ type WorkspaceBotPrompt = {
|
||||
memory?: string | null;
|
||||
};
|
||||
|
||||
type BotPrompt = {
|
||||
/**
|
||||
* The bot's id: how it names itself, and how the user, the crew tools
|
||||
* and every ref in a message point at it.
|
||||
*/
|
||||
id?: number | string | null;
|
||||
name?: string | null;
|
||||
kind?: string | null;
|
||||
/**
|
||||
* The one-line role it does that job as, when it has one.
|
||||
*/
|
||||
role?: string | null;
|
||||
};
|
||||
|
||||
type Params = {
|
||||
/**
|
||||
* The bot itself. Its id is always in the prompt: it is how the bot
|
||||
* names itself and how everything else (crew tools, refs, tasks,
|
||||
* files) points back at it, in chat replies, tasks and routines alike.
|
||||
*/
|
||||
bot?: BotPrompt | null;
|
||||
system_prompt?: string | null;
|
||||
/**
|
||||
* The install-wide text every bot gets, right after its own core
|
||||
@@ -32,14 +52,16 @@ type Params = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The full prompt a bot runs with (markdown): its own core instructions,
|
||||
* then the install-wide appendix, then, in a workspace, the workspace's
|
||||
* The full prompt a bot runs with (markdown): who it is (its name and id,
|
||||
* so it always knows which bot it is), its own core instructions, then the
|
||||
* install-wide appendix, then, in a workspace, the workspace's
|
||||
* context and the bot's instructions there, then its core memory, its
|
||||
* memory about the user and its memory in the workspace (said to be the
|
||||
* bot's own notes, not the workspace's context). Empty parts are left out;
|
||||
* the workspace heading always shows, so the bot knows where it is.
|
||||
*/
|
||||
export default function composeBotPrompt({
|
||||
bot,
|
||||
system_prompt,
|
||||
appendix,
|
||||
memory,
|
||||
@@ -49,6 +71,11 @@ export default function composeBotPrompt({
|
||||
}: Params) {
|
||||
const parts: string[] = [];
|
||||
const name = workspace?.name?.trim() || "Untitled";
|
||||
const identity = composeIdentity({ bot });
|
||||
|
||||
if (identity) {
|
||||
parts.push(identity);
|
||||
}
|
||||
|
||||
if (system_prompt?.trim()) {
|
||||
parts.push(system_prompt.trim());
|
||||
@@ -95,3 +122,24 @@ export default function composeBotPrompt({
|
||||
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Who the bot is, said once at the top of its prompt: its name, kind and
|
||||
* id, and that the id is how it is named. Empty when the caller didn't say
|
||||
* which bot it is.
|
||||
*/
|
||||
function composeIdentity({ bot }: { bot?: BotPrompt | null }) {
|
||||
const id = bot?.id === null || bot?.id === undefined ? "" : String(bot.id).trim();
|
||||
|
||||
if (!id) return "";
|
||||
|
||||
const name = bot?.name?.trim() || "A bot";
|
||||
const kind = bot?.kind == "coordinator" ? "the coordinator" : "a worker bot";
|
||||
const role = bot?.role?.trim();
|
||||
|
||||
return [
|
||||
`# You are ${name}`,
|
||||
`You are ${kind} on this crew.${role ? ` Your role: ${role}.` : ""}`,
|
||||
`Your bot id is ${id}. That id is you: when a tool takes a bot id for the bot it is about (your own tasks, your own picture), this is it, and when you name yourself in a message write the ref \`[bot:${id}]\`, never another bot's.`,
|
||||
].join("\n\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user