Перейти до вмісту

Робочі простори та ізоляція

Робочий простір — це тенант ізоляції верхнього рівня в Control Center. Він об’єднує агентів, репозиторії, простори, тікети, пам’ять, конвеєри й учасників в один обмежений контекст. Усе, що відбувається всередині робочого простору, залишається в ньому.

Типовий робочий простір відповідає одному проєкту або одній кодовій базі. У вас можуть бути окремі робочі простори для «api-service», «frontend-v2» та «infrastructure».

Рядки робочого простору живуть у власному файлі SQLite цього робочого простору, за шляхом <dataDir>/<workspaceId>/workspace.db. Кожен робочий простір отримує каталог, а не голий файл, бо робочий простір накопичує більше, ніж базу даних — worktree для кожної розмови, файли агентів і навичок, облікові дані чат-бота, — і надання йому теки означає, що все це живе й видаляється разом.

Сутність Область
Агенти Належать рівно одному робочому простору
Репозиторії Реєструються в одному робочому просторі (див. нижче)
Простори, розмови та повідомлення У межах робочого простору
Тікети, проєкти, плани, оркестрації У межах робочого простору
Факти пам’яті, політики, домени, дозволи доступу У межах робочого простору
Запуски конвеєрів, шаблони, тригери У межах робочого простору
Стан pull request, простори перевірки, когорти перевірки У межах робочого простору
Журнали запусків агентів і робоча пам’ять У межах робочого простору
Учасники, запрошення, дозволи на окремі репозиторії У межах робочого простору
Символи та ребра графа коду Обмежено repoId у межах файлу робочого простору

Більшість із цих рядків мають стовпець workspaceId; окремі файли баз даних забезпечують ізоляцію. Див. інваріант ізоляції нижче.

Репозиторії належать робочому простору; ідентичність між робочими просторами — за шляхом

Розділ із назвою «Репозиторії належать робочому простору; ідентичність між робочими просторами — за шляхом»

Рядок репозиторію живе у власному файлі бази даних свого робочого простору — файл і є областю, тож таблиця не має стовпця workspaceId і не має таблиці зв’язування.

Ви додаєте репозиторій у робочий простір у розділі Налаштування → Робочий простір → Репозиторії. Реєстрація тієї самої робочої копії у двох робочих просторах створює два незалежні рядки з двома ідентифікаторами; ідентичність репозиторію між робочими просторами визначається шляхом у файловій системі (або GitHub owner/name), ніколи не ідентифікатором.

Реєстрація сувора: шлях має бути всередині робочого дерева git, він має мати віддалене сховище origin, і це віддалене сховище має вказувати на підтримуваний хостинг коду (github.com, gitlab.com або bitbucket.org). Хостинг коду зчитується з віддаленого сховища й зберігається на репозиторії, тож один робочий простір може містити репозиторії з усіх трьох. Усе інше відхиляється. Переглядач тек, з якого ви обираєте, показує файлову систему хоста сервера, обмежену списком дозволених шляхів сервера --repo-roots (який за замовчуванням указує на домашній каталог користувача сервера).

Видалення репозиторію з робочого простору остаточно видаляє рядок і каскадно прибирає його символи коду, ребра, файли, контрольні точки індексу та дозволи для окремих учасників. Робоча копія на диску залишається недоторканою.

Граф коду живе в тому самому файлі з ключем repoId, бо різні робочі простори можуть мати той самий репозиторій на різних гілках із різним кодом.

Витоки даних між робочими просторами вважаються помилками. Ізоляція структурна й забезпечується на кількох рівнях:

  • Рівень бази даних. Кожен робочий простір має власний файл SQLite, який видає WorkspaceDatabaseManager.of(workspaceId). База даних робочого простору не оголошує users, workspaces чи таблиці будь-якого іншого робочого простору, тож читання з чужого робочого простору — це не помилка, яку можна написати: воно не компілюється. Репозиторії розв’язують свій DAO під час кожного виклику, а не кешують його, і оскільки кожен метод репозиторію приймає обов’язковий workspaceId, робочий простір ніколи не можна вивести чи взяти за замовчуванням.
  • Ідентифікатор робочого простору — це сегмент шляху. Оскільки ідентифікатор стає назвою каталогу, він має відповідати безпечному шаблону одного сегмента (починається з літери чи цифри, далі літери, цифри, крапки, підкреслення, дефіси; без ..). В усьому продукті ідентифікатори — це UUID. Будь-що інше викидає помилку, а не санітизується — це захист від обходу шляху, і він падає гучно, а не записує дані робочого простору за шляхом, який обрав хтось інший.
  • Багаторівневий захист. Стовпці workspaceId усе ще на місці й усе ще записуються. Надлишкові в межах одного файлу, вони залишають тригери стрічки синхронізації, індекси FTS і наявні форми рядків без змін і роблять файл самодостатнім для опису, коли його оглядають окремо — саме на це покладаються експорт та імпорт.
  • Доменний рівень. Сервіси, що змінюють сутності за ідентифікатором, перевіряють entity.workspaceId == workspaceId в єдиній точці контролю, перш ніж продовжити, і викидають WorkspaceMismatchException за невідповідності — гучна відмова для ідентифікатора, що надійшов із хибного контексту. Гучно відмовляти важливо: мовчазна відсутність дії приховує помилку, а продовження призводить до витоку.
  • Рівень RPC. Диспетчер repo-RPC — це точка контролю. Кожна операція в межах робочого простору має нести workspace_id у власних аргументах — немає «поточного робочого простору» на сеанс, який міг би протекти, а невідомий ідентифікатор відхиляється як «не знайдено» перш ніж щось відкриє базу даних, тож поганий ідентифікатор не може матеріалізувати фантомний workspace.db. Справді глобальні операції (стрічка новин, черга флоту) оголошують себе без прив’язки до робочого простору як явне, переглянуте рішення. Інструменти MCP дотримуються того самого правила: будь-який інструмент, що торкається даних у межах робочого простору, вимагає workspace_id.
  • Міжпросторове розгалуження піддається переліченню. Відповідь на питання про кілька робочих просторів означає відкриття кількох баз даних. Це розгалуження обмежене одним помічником, CrossWorkspaceQueries, тож повний перелік того, що законно перетинає межу, живе в місцях виклику одного файлу, а не розчиняється за коментарями до коду. Ці місця виклику: агрегація в межах усього сервера, реконсилятори під час запуску, очищення за строком зберігання та GC, резервне копіювання, маршрутизатори подій — і пошук членства нижче.
  • Доступу лише за ідентифікатором недостатньо. Пошук сутності за її UUID не доводить, що вона належить робочому простору того, хто звертається. Розділення файлів робить хибний файл недосяжним; перевірка на доменному рівні ловить неправильний ідентифікатор, що надійшов через легітимний файл.

Членство — межа доступу, а не ключ прив’язки

Розділ із назвою «Членство — межа доступу, а не ключ прив’язки»

Володіння обліковими даними пристрою автентифікує пристрій. Воно ні до чого не уповноважує всередині робочого простору. Кожна операція в межах робочого простору визначає роль членства того, хто звертається, і застосовує нижню межу, виведену з виду операції — читання потребує guest, зміна потребує member, руйнівна операція потребує admin — а операції, що показують вміст репозиторію, додатково перевіряють дозвіл того, хто звертається, на цей репозиторій. Дійсний ключ прив’язки без членства отримує Not a member of this workspace.

Наслідок для ізоляції витончений: рядки членства живуть у власному файлі робочого простору, тож «хто є в цьому робочому просторі?» — це звичайне читання в межах області. Обернене питання — «у яких робочих просторах я перебуваю?» — за визначенням розкидане по файлах, тож це одне з небагатьох санкціонованих розгалужень CrossWorkspaceQueries, і саме його виконує засіб вибору робочого простору, перш ніж жоден робочий простір ще вибрано.

Драбину ролей і дозволи на репозиторії описано в Спільній роботі.

Не все належить робочому простору. Друга база даних, <dataDir>/global.db, містить те, що справді спільне для всього сервера:

  • реєстр робочих просторів, щоб перемикач міг перелічити кожен робочий простір, не відкриваючи жодного файлу робочого простору, — і щоб назва робочого простору, логотип, власник, ручний порядок і позначка м’якого видалення жили поза файлом, який вони описують;
  • ідентичність: користувачі, налаштування користувачів і прив’язані пристрої, бо одна людина — це один користувач у кожному робочому просторі, а прив’язаний пристрій переживає видалення робочого простору;
  • черга флоту (воркери, завдання, журнал розміщення), чий планувальник на кожному такті зіставляє всю чергу з кожним воркером;
  • стрічка новин, яка є для кожного користувача (а не для кожного робочого простору) — додана стрічка супроводжує вас у кожен робочий простір;
  • workspace_routes, індекс до автентифікації, який відповідає на питання «якому робочому простору належить цей ключ?» для точок входу, що надходять лише з секретом чи непрозорим ідентифікатором (хеш запрошення, токен вебхука, глибоке посилання) і без робочого простору. Промах там — це «не знайдено»; резервного сканування навмисно немає;
  • налаштування та ідентичність усієї інсталяції: ідентифікатор інсталяції, налаштування рівня хоста, підключення SSO і керовані політики дій (затискач на рівні інсталяції, який може лише посилювати обмеження робочих просторів).

Під час завантаження відкривається лише цей файл. Файли робочих просторів відкриваються ліниво при першому зверненні, тож витрати на запуск залишаються незмінними, скільки б історії не накопичили робочі простори, а quick_check для кожного файлу виконується під час першого використання, а не на шляху до банера готовності.

Файл робочого простору містить лише однорядковий workspace_meta для самоідентифікації (його ідентифікатор, інсталяцію, що його створила, версію схеми, з якою його створили, коли). Усе описове про робочий простір — у реєстрі.

Створення робочого простору робить більше, ніж вставлення рядка в реєстр. За одну операцію сервер позначає користувача, що створює, як ownerUserId, записує членство цього користувача з роллю власника й публікує доменну подію WorkspaceCreated. Слухачі цієї події потім ідемпотентно й у фоні засівають:

  • агента CEO і чотирьох фахівців — qa, architect, engineer, librarian, — які підпорядковуються CEO;
  • вбудовані шаблони конвеєрів та їхні тригери (тригери подій і ручні увімкнено одразу; заплановані — за бажанням, крім щоденного прибирання worktree через GC);
  • стартові набори eval.

Це відбувається для кожного робочого простору, а не лише для першого. Засіяні агенти створюються без адаптера, тож вони працюють на Control Center (built-in) із типовою моделлю Anthropic (claude-opus-4-8, якщо ви не закріпите іншу), доки ви не задасте адаптер і модель для кожного агента в розділі Налаштування → Робочий простір → Агенти. Див. Модель агента.

Запис про членство власника важить більше, ніж здається: без нього щойно створений робочий простір не мав би жодного учасника, і кожен наступний виклик творця в межах робочого простору було б відхилено.

Видалення робочого простору позначає рядок у реєстрі та відв’язує каталог. Робочий простір зникає з усіх списків і пошуків. Рядок реєстру залишається (м’яко видалений), тож ідентифікатор усе ще відомий резервному копіюванню й обслуговуванню, але workspace.delete негайно відкидає каталог цього робочого простору — базу даних, worktree, файли агентів та решту — через WorkspaceDatabaseManager.dropAndClose. Видалити його може лише власник робочого простору.

Оскільки один робочий простір — це один файл, передати робочий простір — це файлова операція, а не дамп таблиця за таблицею:

  • workspace.export — це один вираз VACUUM INTO: узгоджений, дефрагментований знімок, зроблений у транзакції читання, поки сервер продовжує обслуговувати.
  • workspace.import приймає такий файл, замінюючи все, що цільовий робочий простір містить наразі. Вбудований workspace_meta дає змогу імпорту відрізнити «мій власний файл, прийнятий повторно» від «файла з іншої інсталяції» (останній дозволено, але записується в журнал, а не лишається невидимим).
  • server.backupNow робить знімок усієї інсталяції в каталог із часовою міткою, що повторює живий каталог даних — manifest.json, global.db і один <workspaceId>/workspace.db на кожен робочий простір, — тож відновлення — це копіювання його назад.
  • server.listBackups читає цей каталог назад, від найновішого, із розміром і списком робочих просторів кожного знімка. Знімок, чий маніфест відсутній або вказує на файли, яких немає, усе одно показується з позначкою «неповний» — приховування його — це те, як оператор починає вірити, що має резервну копію, якої не має.

Операції резервного копіювання живуть у розділі Налаштування → Сервер → Резервні копії та відновлення: Зробити знімок зараз створює знімок інсталяції, список показує, які знімки повні, і дає власнику інсталяції видалити знімок після підтвердження, а кожен робочий простір пропонує експорт та імпорт. Вбудований конвеєр Workspace backup бере лише базу даних цього робочого простору за тижневим розкладом і видаляє власні резервні копії, старші за налаштований строк зберігання. Відновлення робочого простору зі знімка інсталяції — це workspace.import, націлений на <workspaceId>/workspace.db цього знімка; відновлення всієї інсталяції залишається копіюванням назад каталогу знімка за зупиненого сервера.

Чого жодна з них не переносить — це решта каталогу робочого простору. Робочий простір накопичує більше, ніж базу даних — вставлені зображення, файли навичок і агентів, облікові дані чату, worktree, — а експорт та імпорт переносять файл, тож усе поруч із ним залишається на місці. Це навмисно для облікових даних і припустимо, що worktree можна відкинути; для решти це обмеження, про яке варто знати, а не можливість. У довідці про резервне копіювання є повна таблиця.

Перенесення резервної копії між сервером і вашим пристроєм

Розділ із назвою «Перенесення резервної копії між сервером і вашим пристроєм»

Кожна з наведених вище операцій говорить шляхами на сервері, що є повною відповіддю лише тоді, коли сервер — це ваша власна машина. Три підписані HTTP-маршрути — /backup/workspace, /backup/snapshot і /backup/restore — переносять байти для будь-якої іншої топології тим самим автентифікованим каналом, який уже використовують медіапроксі та сховище зображень. На сторінці це кнопка Завантажити поряд із кожним експортом і кожним знімком (ви обираєте, куди він потрапить), і Вибрати файл і вивантажити, щоб відновити з файла, що лежить на машині, якою ви користуєтеся. Вивантаження — це те, що працює, коли сервер не ваша машина: workspace.import вимагає, щоб файл уже був на хості, і саме так він там опиняється.

Варто знати три властивості:

  • Завантаження створює свіжу копію, і сервер не зберігає з неї нічого. Завантаження — це не «забрати файл, який створила кнопка експорту»: воно експортує знову, потоково передає його й видаляє. Відповідь має no-store і Accept-Ranges: none з тієї ж причини: відновлений діапазон зшив би два різні експорти в один файл, який виглядає дійсним, але таким не є.
  • Вивантаження потоково пишеться на диск, ніколи не буферизується, на обох кінцях, і проміжна копія видаляється на кожному шляху виходу — включно з відмовою. База даних робочого простору — це не знімок екрана.
  • Ретрансльоване з’єднання не має походження HTTP, тож елементи керування передаванням там вимкнено із заувагою про причину. Усе, що говорить серверними шляхами, продовжує працювати через ретранслятор; прямого з’єднання потребують лише канали передавання байтів.

Ви можете запускати кілька робочих просторів пліч-о-пліч. Кожен працює незалежно: агенти одного робочого простору не можуть писати агентам іншого, не можуть читати пам’ять іншого робочого простору й не бачать його тікетів. Визначення одержувача для обміну повідомленнями між агентами точне й ніколи не перетинає робочий простір.

Панелі між робочими просторами немає. Аналітична поверхня за адресою /workspaces/:workspaceId/observability підписується на обмежений глобальний потік останніх запусків і негайно звужує його до активного робочого простору, тож там ви бачите по одному робочому простору за раз. Єдині справді міжпросторові поверхні, яких торкається людина, — це засіб вибору робочого простору та перемикач.

Одне практичне обмеження, яке варто знати: робочий простір, який хтось використовує, залишається відкритим — LRU немає. Після 32 таких сервер попереджає, а не відмовляє, бо кожен відкритий файл тримає фоновий ізолят і кеш сторінок. Міжпросторове розгалуження відкриває файли на короткий час і закриває їх потому, якщо їх не заявив ніхто інший.