Keep a bot's workspace memory from reading as the workspace's record
A workspace bot asked to "update the workspace record" wrote to its own memory there, called it the workspace's record, and then, when the user said nothing had changed, denied making any write at all: it checked nothing and confessed to work it had done. - Say in the prompt that the workspace's context is the user's, and that memory in a workspace is the bot's own notes, readable in its settings - Say the same in update_memory and update_instructions results, with where the user reads it and that the workspace's own fields didn't change - Have ActionCheckNote look at what actually happened before agreeing with a user that nothing was done, and never deny work that happened
This commit is contained in:
@@ -47,6 +47,7 @@ Every action counts here: saving memory or instructions, creating or changing a
|
|||||||
- Before you create anything, look for one that already exists: find_bot for bots, find_workspace for workspaces, list_schedules for schedules, your memory and instructions in this prompt, and for anything outside (files, records, emails, events) list or search it with the tools you have. If it's there, update it instead of making a second one. Create something new only once you've checked there's none.
|
- Before you create anything, look for one that already exists: find_bot for bots, find_workspace for workspaces, list_schedules for schedules, your memory and instructions in this prompt, and for anything outside (files, records, emails, events) list or search it with the tools you have. If it's there, update it instead of making a second one. Create something new only once you've checked there's none.
|
||||||
- After you act, verify it took effect before you tell the user it's done: read it back with the same lookups, check the tool's result, or on a computer re-read the file, page or sent folder. Only say "done" for what you verified.
|
- After you act, verify it took effect before you tell the user it's done: read it back with the same lookups, check the tool's result, or on a computer re-read the file, page or sent folder. Only say "done" for what you verified.
|
||||||
- If you couldn't verify it, say so plainly instead of claiming it's done. If it didn't take, try again once, then report what failed.
|
- If you couldn't verify it, say so plainly instead of claiming it's done. If it didn't take, try again once, then report what failed.
|
||||||
|
- Never deny something you did either. When the user says a change isn't where they looked, check what actually happened before you answer: the tool results in this conversation, and what you saved. Then say what you did and where it went, and if it went somewhere other than where they looked, point them to it. Never take the blame for work that happened, and never write a false account of it into your memory or instructions; if you already wrote one, correct it with update_memory.
|
||||||
- Starting or handing over a task isn't finishing it: say it's under way, not done.`;
|
- Starting or handing over a task isn't finishing it: say it's under way, not done.`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function updateInstructionsTool({
|
|||||||
return {
|
return {
|
||||||
name: "update_instructions",
|
name: "update_instructions",
|
||||||
description: workspace_id
|
description: workspace_id
|
||||||
? `Replace your instructions with this full text (markdown). scope "workspace" (the default) is your instructions for this workspace, under "# Your instructions in ..."; "core" is your core instructions, the start of your prompt in every workspace and your own chat. Both are shared: the workspace's with everyone working in it, your core ones with every user you work for. Change them only for how you should work for all of them, such as a standing rule for your job; a preference of this one user goes in your memory of them (update_memory, scope "user"). Keep everything that still applies.`
|
? `Replace your instructions with this full text (markdown). scope "workspace" (the default) is your instructions for this workspace, under "# Your instructions in ...": yours there, not the workspace's description or context; "core" is your core instructions, the start of your prompt in every workspace and your own chat. Both are shared: the workspace's with everyone working in it, your core ones with every user you work for. Change them only for how you should work for all of them, such as a standing rule for your job; a preference of this one user goes in your memory of them (update_memory, scope "user"). Keep everything that still applies.`
|
||||||
: `Replace your core instructions with this full text (markdown): the start of your prompt, before any workspace or memory, shared with every user you work for. Change them only for how you should work for every user you work for, such as a standing rule for your job; a preference of this one user goes in your memory of them (update_memory, scope "user"). Keep everything that still applies.`,
|
: `Replace your core instructions with this full text (markdown): the start of your prompt, before any workspace or memory, shared with every user you work for. Change them only for how you should work for every user you work for, such as a standing rule for your job; a preference of this one user goes in your memory of them (update_memory, scope "user"). Keep everything that still applies.`,
|
||||||
schema: workspace_id
|
schema: workspace_id
|
||||||
? z.object({ instructions, scope: z.enum(["workspace", "core"]).default("workspace") })
|
? z.object({ instructions, scope: z.enum(["workspace", "core"]).default("workspace") })
|
||||||
@@ -54,7 +54,7 @@ export default function updateInstructionsTool({
|
|||||||
workspace_id,
|
workspace_id,
|
||||||
expected: args.instructions,
|
expected: args.instructions,
|
||||||
});
|
});
|
||||||
return `Instructions for this workspace saved and checked. They apply from your next run here.`;
|
return `Instructions for this workspace saved and checked. They apply from your next run here. They are yours in this workspace, not its context: the workspace's own description and context did not change.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateBotSystemPrompt({
|
await updateBotSystemPrompt({
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ const UserScopeNote = `what you remember about this user, under "# Memory about
|
|||||||
|
|
||||||
const CoreScopeNote = `scope "core" is your core memory, under "# Memory", shared with every user you work for: only general knowledge about your job that holds for anyone, never anything about a particular user.`;
|
const CoreScopeNote = `scope "core" is your core memory, under "# Memory", shared with every user you work for: only general knowledge about your job that holds for anyone, never anything about a particular user.`;
|
||||||
|
|
||||||
|
const WorkspaceScopeNote = `scope "workspace" (the default) is your own memory for this workspace, under "# Memory in ...": what holds for the work here. It is yours, not the workspace's description or context, which the user sets and which you never change from here.`;
|
||||||
|
|
||||||
const ReplaceNote = `Anything you leave out is forgotten, so include the parts that still matter.`;
|
const ReplaceNote = `Anything you leave out is forgotten, so include the parts that still matter.`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +47,7 @@ export default function updateMemoryTool({
|
|||||||
return {
|
return {
|
||||||
name: "update_memory",
|
name: "update_memory",
|
||||||
description: workspace_id
|
description: workspace_id
|
||||||
? `Replace a memory with this full text (markdown). scope "workspace" (the default) is your memory for this workspace, under "# Memory in ...": what holds for the work here. scope "user" is ${UserScopeNote} ${CoreScopeNote} ${ReplaceNote}`
|
? `Replace a memory with this full text (markdown). ${WorkspaceScopeNote} scope "user" is ${UserScopeNote} ${CoreScopeNote} ${ReplaceNote}`
|
||||||
: `Replace a memory with this full text (markdown). scope "user" (the default) is ${UserScopeNote} ${CoreScopeNote} ${ReplaceNote}`,
|
: `Replace a memory with this full text (markdown). scope "user" (the default) is ${UserScopeNote} ${CoreScopeNote} ${ReplaceNote}`,
|
||||||
schema: workspace_id
|
schema: workspace_id
|
||||||
? z.object({ memory, scope: z.enum(["workspace", "user", "core"]).default("workspace") })
|
? z.object({ memory, scope: z.enum(["workspace", "user", "core"]).default("workspace") })
|
||||||
@@ -60,7 +62,7 @@ export default function updateMemoryTool({
|
|||||||
if (workspace_id && args.scope == "workspace") {
|
if (workspace_id && args.scope == "workspace") {
|
||||||
await updateWorkspaceBot({ user_id, workspace_id, bot_id, memory: args.memory });
|
await updateWorkspaceBot({ user_id, workspace_id, bot_id, memory: args.memory });
|
||||||
await checkSavedText({ kind: "workspace_memory", bot_id, workspace_id, expected: args.memory });
|
await checkSavedText({ kind: "workspace_memory", bot_id, workspace_id, expected: args.memory });
|
||||||
return `Memory for this workspace saved and checked.`;
|
return `Memory for this workspace saved and checked. It is your own memory, not the workspace's description or context: those did not change. The user reads it in the workspace's settings, under this bot's row. Tell the user that, never that you changed the workspace.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateBotUserMemory({ user_id, bot_id, memory: args.memory });
|
await updateBotUserMemory({ user_id, bot_id, memory: args.memory });
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ describe("composeBotPrompt", () => {
|
|||||||
).toBe(
|
).toBe(
|
||||||
[
|
[
|
||||||
"# You are Scout\n\nFind things.",
|
"# 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 spring launch.\n\nShip by Friday.`,
|
`# 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.",
|
"# Your instructions in Launch\n\nOwn the changelog.",
|
||||||
"# Memory\n\n- Cite sources.",
|
"# Memory\n\n- Cite sources.",
|
||||||
"# Memory about the user\n\n- The user prefers short replies.",
|
"# Memory about the user\n\n- The user prefers short replies.",
|
||||||
"# Memory in Launch\n\n- Marketing signs off on copy.",
|
`# Memory in Launch\n\nYour own notes for this workspace: what you have learned here. They are not the workspace's description or context, which the user sets above; never call them the workspace's record. The user reads them in the workspace's settings, under this bot's row.\n\n- Marketing signs off on copy.`,
|
||||||
].join("\n\n"),
|
].join("\n\n"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ type Params = {
|
|||||||
* The full prompt a bot runs with (markdown): its own core instructions,
|
* The full prompt a bot runs with (markdown): its own core instructions,
|
||||||
* then, in a workspace, the workspace's context and the bot's
|
* then, in a workspace, the workspace's context and the bot's
|
||||||
* instructions there, then its core memory, its memory about the user and
|
* instructions there, then its core memory, its memory about the user and
|
||||||
* its memory in the workspace. Empty parts are left out; the workspace
|
* its memory in the workspace (said to be the bot's own notes, not the
|
||||||
* heading always shows, so the bot knows where it is.
|
* workspace's context). Empty parts are left out; the workspace heading
|
||||||
|
* always shows, so the bot knows where it is.
|
||||||
*/
|
*/
|
||||||
export default function composeBotPrompt({
|
export default function composeBotPrompt({
|
||||||
system_prompt,
|
system_prompt,
|
||||||
@@ -48,12 +49,16 @@ export default function composeBotPrompt({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
|
const context = [workspace.description?.trim(), workspace.system_prompt?.trim()].filter(
|
||||||
|
Boolean,
|
||||||
|
) as string[];
|
||||||
|
|
||||||
parts.push(
|
parts.push(
|
||||||
[
|
[
|
||||||
`# Workspace: ${name}`,
|
`# Workspace: ${name}`,
|
||||||
`You're working in the workspace "${name}". Follow its context and your instructions for it below, over your general habits where they differ.`,
|
`You're working in the workspace "${name}". Follow its context and your instructions for it below, over your general habits where they differ.`,
|
||||||
workspace.description?.trim() || "",
|
context.length ? "The workspace's context, which the user sets:" : "",
|
||||||
workspace.system_prompt?.trim() || "",
|
...context,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n"),
|
.join("\n\n"),
|
||||||
@@ -73,7 +78,9 @@ export default function composeBotPrompt({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (workspace && workspace_bot?.memory?.trim()) {
|
if (workspace && workspace_bot?.memory?.trim()) {
|
||||||
parts.push(`# Memory in ${name}\n\n${workspace_bot.memory.trim()}`);
|
parts.push(
|
||||||
|
`# Memory in ${name}\n\nYour own notes for this workspace: what you have learned here. They are not the workspace's description or context, which the user sets above; never call them the workspace's record. The user reads them in the workspace's settings, under this bot's row.\n\n${workspace_bot.memory.trim()}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join("\n\n");
|
return parts.join("\n\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user