Let workers ask each other through the coordinator in chatrooms
A worker asks for another bot's help with ask_crew. The coordinator reads the request, asks the right bot (in a workspace, only its members), and sends the answer back into the asking bot's conversation, waking it. Each exchange is kept as a read-only chatroom: listed in the main rail after the workspaces, or in the workspace's rail for requests made there, with live status rows for each bot while it's open and a Stop button. Room sessions are hidden from every conversation list, other bots' words reach the model fenced as data, and turns, nesting and open rooms are capped. Closed rooms are pruned after a retention period and past a cap per place (Settings > General). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { z } from "zod";
|
||||
import grabBotById from "@/src/functions/backend/db/bots/grab-bot-by-id";
|
||||
import chatroomAskableBots from "@/src/functions/backend/db/chatrooms/chatroom-askable-bots";
|
||||
import createChatroom from "@/src/functions/backend/db/chatrooms/create-chatroom";
|
||||
import chatroomRef from "@/src/utils/chatroom-ref";
|
||||
import type { HarnessTool } from "../../types";
|
||||
|
||||
type Params = {
|
||||
user_id: number | string;
|
||||
/**
|
||||
* The worker asking.
|
||||
*/
|
||||
bot_id: number | string;
|
||||
/**
|
||||
* The session it asks from: the answer comes back there.
|
||||
*/
|
||||
session_id: number | string;
|
||||
/**
|
||||
* Where it works: in a workspace, only its members can be asked.
|
||||
*/
|
||||
workspace_id?: number | string | null;
|
||||
/**
|
||||
* Called with the room's ref, so the reply always carries its chip.
|
||||
*/
|
||||
onChatroom?: (params: { ref: string }) => void;
|
||||
};
|
||||
|
||||
type Args = {
|
||||
title: string;
|
||||
request: string;
|
||||
bot_id?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets a worker ask another bot for something, only ever through the
|
||||
* coordinator: it opens a chatroom (`createChatroom`) where the
|
||||
* coordinator works out who should answer, asks them, and sends the answer
|
||||
* back to this conversation, which wakes the bot to act on it. Who can be
|
||||
* asked is decided by the app, never the model: in a workspace its
|
||||
* members, otherwise every worker the user reaches.
|
||||
*/
|
||||
export default function askCrewTool({ user_id, bot_id, session_id, workspace_id, onChatroom }: Params): HarnessTool<Args> {
|
||||
return {
|
||||
name: "ask_crew",
|
||||
description: `Ask another bot for something you need (what it knows, a file it has, work in its line) through the coordinator. The coordinator reads your request, asks the bot best placed to answer and sends the answer back here as a message, which wakes you to act on it: don't wait or check on it. Write the request as a full brief: what you need, why, and what a good answer looks like. Name a bot with bot_id if you know who can help; the coordinator decides. ${
|
||||
workspace_id ? "You're in a workspace, so only its members can be asked." : "Any bot the user works with can be asked."
|
||||
} The request and everything said about it is kept in a chatroom the user can read.`,
|
||||
schema: z.object({
|
||||
title: z.string().trim().min(1).max(80).describe("A short label for the request"),
|
||||
request: z.string().trim().min(1).max(8000).describe("The brief, in markdown"),
|
||||
bot_id: z.number().int().optional().describe("The bot you think can help, if you know"),
|
||||
}),
|
||||
run: async ({ args }) => {
|
||||
const bot = await grabBotById({ bot_id });
|
||||
|
||||
if (!bot?.id) {
|
||||
throw new Error(`This bot no longer exists.`);
|
||||
}
|
||||
|
||||
const askable = await chatroomAskableBots({
|
||||
user_id,
|
||||
requester_bot_id: bot.id,
|
||||
workspace_id,
|
||||
});
|
||||
|
||||
if (!askable.length) {
|
||||
throw new Error(
|
||||
workspace_id
|
||||
? `There are no other bots in this workspace to ask.`
|
||||
: `There are no other bots the user works with to ask.`,
|
||||
);
|
||||
}
|
||||
|
||||
const named = args.bot_id ? askable.find((other) => other.id == args.bot_id) : null;
|
||||
|
||||
if (args.bot_id && !named) {
|
||||
throw new Error(
|
||||
`Bot ${args.bot_id} can't be asked from here. You can ask: ${askable
|
||||
.map((other) => `${other.id}: ${other.name}`)
|
||||
.join("; ")}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const request = named ? `${args.request}\n\n(I think ${named.name} [bot:${named.id}] can help.)` : args.request;
|
||||
|
||||
const room = await createChatroom({
|
||||
user_id,
|
||||
requester: bot,
|
||||
origin_session_id: session_id,
|
||||
title: args.title,
|
||||
request,
|
||||
});
|
||||
|
||||
const ref = chatroomRef({ id: room.id! });
|
||||
|
||||
onChatroom?.({ ref });
|
||||
|
||||
return `Asked the coordinator in ${ref}. The answer comes back here as a message and you'll be woken for it. Tell the user you asked; don't ask again while it's open.`;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -71,6 +71,10 @@ type Params = {
|
||||
* the reply can say so in its prompt.
|
||||
*/
|
||||
onWorkspaceAdmin?: (params: WorkspaceAdminTools) => void;
|
||||
/**
|
||||
* Called with the ref of each chatroom the bot opened (`ask_crew`).
|
||||
*/
|
||||
onChatroom?: (params: { ref: string }) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,6 +112,7 @@ export default async function grabBotChatTools({
|
||||
onTakeOver,
|
||||
onPropose,
|
||||
onWorkspaceAdmin,
|
||||
onChatroom,
|
||||
}: Params): Promise<HarnessTool[]> {
|
||||
const bot_id = bot.id || 0;
|
||||
const tools = [
|
||||
@@ -121,6 +126,7 @@ export default async function grabBotChatTools({
|
||||
admin_workspace_ids: powers.admin_workspace_ids,
|
||||
scopes,
|
||||
onPropose,
|
||||
onChatroom,
|
||||
})),
|
||||
startTaskTool({
|
||||
user_id,
|
||||
|
||||
@@ -11,6 +11,7 @@ import updateInstructionsTool from "./update-instructions-tool";
|
||||
import updateMemoryTool from "./update-memory-tool";
|
||||
import reactToMessageTool from "./react-to-message-tool";
|
||||
import setPictureTool from "./set-picture-tool";
|
||||
import askCrewTool from "./ask-crew-tool";
|
||||
import type { StorageScope } from "@/src/utils/storage-access";
|
||||
|
||||
type Params = {
|
||||
@@ -52,12 +53,18 @@ type Params = {
|
||||
* carries its card. Without it the bot can't propose hooks.
|
||||
*/
|
||||
onPropose?: (params: { ref: string }) => void;
|
||||
/**
|
||||
* In a chat reply: called with the ref of each chatroom the bot opened
|
||||
* (`ask_crew`), so the reply carries its chip.
|
||||
*/
|
||||
onChatroom?: (params: { ref: string }) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The tools a bot works with on every run, bound to that bot and where it
|
||||
* works: its memory and instructions, its schedules, its hooks
|
||||
* (`grabBotHookTools`), and in a workspace with several computers
|
||||
* (`grabBotHookTools`), for a worker ask_crew (another bot, through the
|
||||
* coordinator), and in a workspace with several computers
|
||||
* switching the default one.
|
||||
*/
|
||||
export default async function grabBotTools({
|
||||
@@ -71,6 +78,7 @@ export default async function grabBotTools({
|
||||
scopes = [],
|
||||
hooked,
|
||||
onPropose,
|
||||
onChatroom,
|
||||
}: Params): Promise<HarnessTool[]> {
|
||||
return [
|
||||
setPictureTool({ user_id, bot_id, scopes, is_coordinator }),
|
||||
@@ -82,6 +90,13 @@ export default async function grabBotTools({
|
||||
deleteScheduleTool({ user_id, bot_id }),
|
||||
...grabBotHookTools({ user_id, bot_id, session_id, workspace_id, computers, hooked, onPropose }),
|
||||
...(session_id ? [reactToMessageTool({ bot_id, user_id, session_id })] : []),
|
||||
/**
|
||||
* A worker asks the crew only through the coordinator, never from
|
||||
* a hook call's run, whose payload is someone else's words.
|
||||
*/
|
||||
...(session_id && !is_coordinator && !hooked
|
||||
? [askCrewTool({ user_id, bot_id, session_id, workspace_id, onChatroom })]
|
||||
: []),
|
||||
...(workspace_id && computers.length > 1
|
||||
? [setWorkspaceComputerTool({ user_id, workspace_id, computers })]
|
||||
: []),
|
||||
|
||||
Reference in New Issue
Block a user