Share one crew across the install, keep user memory per user

Bots, workspaces and computers now belong to the install. Users reach
workers only through bot_users (assigned by a super admin or admin) and
workspace_users; the coordinator and main computer are one per install,
and only the super admin sees the coordinator. Conversations are per
user (bot_sessions.user_id), jobs run for the session's user, and live
updates go only to who should see them.

A bot's core memory is now general knowledge shared by every user it
works for; what it learns about a user lives in bot_user_memories.

One-off scripts move existing installs over: move-bot-memory-to-owners
(run first), then share-crew.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 11:37:14 +01:00
co-authored by Claude Opus 5.5
parent 4dd34ed251
commit e45d54d30b
133 changed files with 1854 additions and 436 deletions
+158 -26
View File
@@ -208,15 +208,8 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
fields: [
{
fieldName: "user_id",
fieldDescription: `Bot owner`,
fieldDescription: `Who created the bot. Not a foreign key, and not access: bots belong to the install, reached through \`bot_users\` and \`workspace_users\``,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_user_fk",
},
},
{
fieldName: "name",
@@ -259,7 +252,7 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
{
fieldName: "memory",
fieldDescription: `What the bot must always remember, in markdown. The bot keeps it up to date`,
fieldDescription: `What the bot must always remember, in markdown, the same for every user it works with: never about one user (that's \`bot_user_memories\`). The bot keeps it up to date`,
dataType: "TEXT",
},
{
@@ -282,8 +275,20 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
{
tableName: "bot_sessions",
tableDescription: `A stretch of one conversation: a bot in its own chat (no workspace) or in one workspace. Only the active session's messages go to the model; closed ones can be searched and reopened`,
tableDescription: `A stretch of one user's conversation with a bot: in its own chat (no workspace) or in one workspace. Every user has their own. Only the active session's messages go to the model; closed ones can be searched and reopened`,
fields: [
{
fieldName: "user_id",
fieldDescription: `The user the conversation is with: only they see it, and the bot's runs in it work for them`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_session_user_fk",
},
},
{
fieldName: "bot_id",
fieldDescription: `The bot`,
@@ -351,6 +356,10 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
indexName: "idx_bot_sessions_workspace_id",
indexTableFields: ["workspace_id"],
},
{
indexName: "idx_bot_sessions_user_id",
indexTableFields: ["user_id", "bot_id", "workspace_id", "status"],
},
],
},
{
@@ -825,15 +834,8 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
fields: [
{
fieldName: "user_id",
fieldDescription: `Workspace owner`,
fieldDescription: `Who created the workspace. Not a foreign key, and not access: workspaces belong to the install, reached through \`workspace_users\``,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "workspace_user_fk",
},
},
{
fieldName: "name",
@@ -925,6 +927,143 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
],
},
{
tableName: "workspace_users",
tableDescription: `Users added to a workspace (by a super admin or admin): they chat with every worker in it there`,
fields: [
{
fieldName: "workspace_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "workspaces",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "workspace_users_workspace_fk",
},
},
{
fieldName: "user_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "workspace_users_user_fk",
},
},
],
uniqueConstraints: [
{
alias: "one_user_per_workspace",
constraintName: "A user joins a workspace once",
constraintTableFields: [
{ value: "workspace_id" },
{ value: "user_id" },
],
},
],
indexes: [
{
indexName: "idx_workspace_users_user_id",
indexTableFields: ["user_id"],
},
],
},
{
tableName: "bot_users",
tableDescription: `Worker bots assigned to a user (by a super admin or admin): the user chats with the bot in its own chat. Nobody but the super admin reaches the coordinator`,
fields: [
{
fieldName: "bot_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "bots",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_users_bot_fk",
},
},
{
fieldName: "user_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_users_user_fk",
},
},
],
uniqueConstraints: [
{
alias: "one_user_per_bot",
constraintName: "A bot is assigned to a user once",
constraintTableFields: [
{ value: "bot_id" },
{ value: "user_id" },
],
},
],
indexes: [
{
indexName: "idx_bot_users_user_id",
indexTableFields: ["user_id"],
},
],
},
{
tableName: "bot_user_memories",
tableDescription: `What a bot remembers about one user (markdown). Only that user's runs see it, the way a workspace's memory stays in the workspace`,
fields: [
{
fieldName: "bot_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "bots",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_user_memory_bot_fk",
},
},
{
fieldName: "user_id",
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "bot_user_memory_user_fk",
},
},
{
fieldName: "memory",
fieldDescription: `What the bot must remember about this user (markdown), after its core memory. The bot keeps it up to date`,
dataType: "TEXT",
},
],
uniqueConstraints: [
{
alias: "one_memory_per_bot_user",
constraintName: "A bot has one memory per user",
constraintTableFields: [
{ value: "bot_id" },
{ value: "user_id" },
],
},
],
indexes: [
{
indexName: "idx_bot_user_memories_user_id",
indexTableFields: ["user_id"],
},
],
},
{
tableName: "bot_message_media",
tableDescription: `Files attached to a chat message (\`media\` rows, private to the message's owner)`,
@@ -1039,15 +1178,8 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
fields: [
{
fieldName: "user_id",
fieldDescription: `Computer owner`,
fieldDescription: `Who created the computer. Not a foreign key, and not access: computers belong to the install, reached through the bots that use them`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "computer_user_fk",
},
},
{
fieldName: "container_id",
+67 -5
View File
@@ -15,6 +15,9 @@ export const BunSQLiteTables = [
"cron_jobs",
"workspaces",
"workspace_bots",
"workspace_users",
"bot_users",
"bot_user_memories",
"bot_message_media",
"containers",
"computers",
@@ -160,7 +163,7 @@ export type BUN_SQLITE_COBBLE_BOTS = {
*/
updated_at?: number | "";
/**
* Bot owner
* Who created the bot. Not a foreign key, and not access: bots belong to the install, reached through `bot_users` and `workspace_users`
*/
user_id?: number | "";
name?: string;
@@ -189,7 +192,7 @@ export type BUN_SQLITE_COBBLE_BOTS = {
*/
system_prompt?: string;
/**
* What the bot must always remember, in markdown. The bot keeps it up to date
* What the bot must always remember, in markdown, the same for every user it works with: never about one user (that's `bot_user_memories`). The bot keeps it up to date
*/
memory?: string;
/**
@@ -215,6 +218,10 @@ export type BUN_SQLITE_COBBLE_BOT_SESSIONS = {
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* The user the conversation is with: only they see it, and the bot's runs in it work for them
*/
user_id?: number | "";
/**
* The bot
*/
@@ -543,7 +550,7 @@ export type BUN_SQLITE_COBBLE_WORKSPACES = {
*/
updated_at?: number | "";
/**
* Workspace owner
* Who created the workspace. Not a foreign key, and not access: workspaces belong to the install, reached through `workspace_users`
*/
user_id?: number | "";
name?: string;
@@ -588,6 +595,61 @@ export type BUN_SQLITE_COBBLE_WORKSPACE_BOTS = {
memory?: string;
}
export type BUN_SQLITE_COBBLE_WORKSPACE_USERS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
workspace_id?: number | "";
user_id?: number | "";
}
export type BUN_SQLITE_COBBLE_BOT_USERS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
bot_id?: number | "";
user_id?: number | "";
}
export type BUN_SQLITE_COBBLE_BOT_USER_MEMORIES = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
bot_id?: number | "";
user_id?: number | "";
/**
* What the bot must remember about this user (markdown), after its core memory. The bot keeps it up to date
*/
memory?: string;
}
export type BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA = {
/**
* The unique identifier of the record.
@@ -675,7 +737,7 @@ export type BUN_SQLITE_COBBLE_COMPUTERS = {
*/
updated_at?: number | "";
/**
* Computer owner
* Who created the computer. Not a foreign key, and not access: computers belong to the install, reached through the bots that use them
*/
user_id?: number | "";
/**
@@ -1040,4 +1102,4 @@ export type BUN_SQLITE_COBBLE_ENVS = {
expose_to_computers?: 0 | 1 | "";
}
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_SESSIONS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_BOTS_QUEUE_UPDATES & BUN_SQLITE_COBBLE_CRON_JOBS & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_HARNESS_PROVIDERS & BUN_SQLITE_COBBLE_HARNESS_MODELS & BUN_SQLITE_COBBLE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_WORKSPACE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_LOGS & BUN_SQLITE_COBBLE_ENVS
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_SESSIONS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_BOTS_QUEUE_UPDATES & BUN_SQLITE_COBBLE_CRON_JOBS & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_WORKSPACE_USERS & BUN_SQLITE_COBBLE_BOT_USERS & BUN_SQLITE_COBBLE_BOT_USER_MEMORIES & BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_HARNESS_PROVIDERS & BUN_SQLITE_COBBLE_HARNESS_MODELS & BUN_SQLITE_COBBLE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_WORKSPACE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_LOGS & BUN_SQLITE_COBBLE_ENVS