Ruang kerja dan isolasi
Apa itu ruang kerja?
Bagian berjudul “Apa itu ruang kerja?”Ruang kerja adalah penyewa isolasi tingkat teratas di Control Center. Ruang kerja mengelompokkan agen, repositori, space, tiket, memori, pipeline, dan anggota ke dalam satu konteks yang terbatas. Semua yang terjadi di dalam sebuah ruang kerja tetap berada di dalamnya.
Ruang kerja pada umumnya memetakan ke satu proyek atau satu basis kode. Anda mungkin memiliki ruang kerja terpisah untuk “api-service”, “frontend-v2” dan “infrastructure”.
Data bercakupan ruang kerja
Bagian berjudul “Data bercakupan ruang kerja”Baris-baris milik sebuah ruang kerja berada di berkas SQLite ruang kerja itu sendiri, di <dataDir>/<workspaceId>/workspace.db. Setiap ruang kerja mendapat sebuah direktori dan bukan sekadar berkas, karena ruang kerja mengakumulasi lebih dari sekadar basis data — worktree per percakapan, berkas agen dan skill, kredensial bot chat — dan memberinya sebuah folder berarti semua itu hidup dan terhapus bersama-sama.
| Entitas | Cakupan |
|---|---|
| Agen | Tergolong tepat pada satu ruang kerja |
| Repositori | Terdaftar ke dalam satu ruang kerja (lihat di bawah) |
| Space, percakapan, dan pesan | Bercakupan ruang kerja |
| Tiket, proyek, rencana, orkestrasi | Bercakupan ruang kerja |
| Fakta memori, kebijakan, domain, pemberian akses | Bercakupan ruang kerja |
| Eksekusi pipeline, templat, pemicu | Bercakupan ruang kerja |
| Status pull request, space tinjauan, kohort tinjauan | Bercakupan ruang kerja |
| Log run agen dan memori kerja | Bercakupan ruang kerja |
| Anggota, undangan, pemberian izin per repo | Bercakupan ruang kerja |
| Simbol dan edge graf kode | Bercakupan menurut repoId di dalam berkas ruang kerja |
Sebagian besar baris ini membawa kolom workspaceId; berkas basis data yang terpisah menegakkan isolasi. Lihat invarian isolasi di bawah.
Repo bercakupan ruang kerja; identitas lintas ruang kerja berdasarkan jalur
Bagian berjudul “Repo bercakupan ruang kerja; identitas lintas ruang kerja berdasarkan jalur”Baris repo berada di berkas basis data ruang kerjanya sendiri — berkas itulah cakupannya, jadi tabelnya tidak memiliki kolom workspaceId maupun tabel join.
Anda menambahkan repo ke sebuah ruang kerja di Pengaturan → Ruang kerja → Repositori. Mendaftarkan checkout yang sama ke dua ruang kerja akan membuat dua baris independen dengan dua id; identitas repo lintas ruang kerja ditentukan oleh jalur sistem berkas (atau owner/name GitHub), tidak pernah oleh id.
Pendaftarannya ketat: jalurnya harus berada di dalam work tree git, harus memiliki remote origin, dan remote itu harus menunjuk ke forge yang didukung (github.com, gitlab.com, atau bitbucket.org). Forge-nya dibaca dari remote dan disimpan pada repo, sehingga satu ruang kerja dapat memuat repo dari ketiganya. Apa pun selain itu ditolak. Penjelajah folder tempat Anda memilih akan mendaftar sistem berkas host server, dibatasi pada daftar izin --repo-roots milik server (yang secara default adalah direktori home pengguna server).
Menghapus sebuah repositori dari ruang kerja akan menghapus barisnya sekaligus dan menggugurkan simbol kode, edge, berkas, checkpoint indeks, serta pemberian izin per anggota miliknya. Checkout di disk tidak tersentuh.
Graf kode berada di berkas yang sama, dikunci berdasarkan repoId, karena ruang kerja yang berbeda dapat memiliki repositori yang sama pada cabang berbeda dengan kode yang berbeda.
Invarian isolasi
Bagian berjudul “Invarian isolasi”Kebocoran data lintas ruang kerja diperlakukan sebagai bug. Isolasi bersifat struktural, ditegakkan pada beberapa lapisan:
- Lapisan basis data. Setiap ruang kerja memiliki berkas SQLite sendiri, yang dibagikan oleh
WorkspaceDatabaseManager.of(workspaceId). Basis data sebuah ruang kerja tidak mendeklarasikan tabelusers,workspaces, atau tabel ruang kerja lain mana pun, sehingga pembacaan lintas ruang kerja bukanlah bug yang bisa Anda tulis — kode itu tidak dapat dikompilasi. Repositori me-resolve DAO-nya per pemanggilan alih-alih menyimpannya di cache, dan karena setiap metode repositori menerimaworkspaceIdyang wajib, ruang kerja tidak akan pernah bisa disimpulkan atau memakai nilai default. - Id ruang kerja adalah segmen jalur. Karena id-nya menjadi nama direktori, id tersebut harus cocok dengan pola satu segmen yang aman (diawali alfanumerik, lalu alfanumerik, titik, garis bawah, tanda hubung; tanpa
..). Di seluruh produk, id-nya berupa UUID. Apa pun selain itu akan melempar pengecualian alih-alih disanitasi — itulah pengaman path traversal dan ia gagal dengan berisik alih-alih menuliskan data sebuah ruang kerja ke jalur yang dipilih orang lain. - Pertahanan berlapis. Kolom
workspaceIdtetap ada dan tetap ditulis. Redundan di dalam satu berkas, kolom itu menjaga trigger sync-feed, indeks FTS, dan bentuk baris yang ada tetap tidak berubah, sekaligus membuat berkas dapat menjelaskan dirinya sendiri saat diperiksa secara mandiri — dan itulah yang menjadi andalan ekspor dan impor. - Lapisan domain. Layanan yang memutasi entitas berdasarkan id memvalidasi
entity.workspaceId == workspaceIddi satu titik pengunci sebelum melanjutkan dan melemparWorkspaceMismatchExceptionsaat tidak cocok — kegagalan yang berisik untuk id yang datang dari konteks yang salah. Menolak dengan berisik itu penting: no-op yang senyap menyembunyikan bug, sedangkan melanjutkan berarti membocorkan data. - Lapisan RPC. Dispatcher repo-RPC adalah titik penguncinya. Setiap op bercakupan ruang kerja harus membawa
workspace_iddi argumennya sendiri — tidak ada “ruang kerja saat ini” per sesi yang bisa bocor, dan id yang tidak dikenal ditolak sebagai not-found sebelum apa pun membuka basis data, sehingga id yang buruk tidak dapat mewujudkanworkspace.dbhantu. Op yang benar-benar global (newsfeed, antrean fleet) mendeklarasikan dirinya tanpa cakupan sebagai keputusan eksplisit yang telah ditinjau. Alat MCP mengikuti aturan yang sama: alat apa pun yang menyentuh data bercakupan ruang kerja mewajibkanworkspace_id. - Fan-out lintas ruang kerja dapat dienumerasi. Menjawab pertanyaan tentang beberapa ruang kerja berarti membuka beberapa basis data. Fan-out itu dibatasi pada satu helper,
CrossWorkspaceQueries, sehingga daftar lengkap hal-hal yang sah melintasi batas tersebut berada di titik pemanggilan satu berkas alih-alih menyebar di balik komentar dokumentasi. Titik pemanggilan itu adalah: agregasi seluruh server, rekonsiliator saat mulai, sapuan retensi dan GC, cadangan, router peristiwa — dan pencarian keanggotaan di bawah. - Akses hanya dengan id tidak cukup. Mencari entitas berdasarkan UUID-nya tidak membuktikan bahwa entitas itu milik ruang kerja pemanggil. Pemisahan berkas membuat berkas yang salah tidak terjangkau; validasi lapisan domain menangkap id yang salah yang datang melalui berkas yang sah.
Keanggotaan, bukan kunci pemasangan, yang memberikan akses
Bagian berjudul “Keanggotaan, bukan kunci pemasangan, yang memberikan akses”Memegang kredensial perangkat mengautentikasi sebuah perangkat. Itu tidak memberi wewenang apa pun di dalam sebuah ruang kerja. Setiap op bercakupan ruang kerja me-resolve peran keanggotaan pemanggil dan menegakkan batas bawah yang diturunkan dari jenis op-nya — pembacaan membutuhkan guest, mutasi membutuhkan member, operasi destruktif membutuhkan admin — dan op yang menampilkan konten repo juga memeriksa pemberian izin per repo milik pemanggil. Kunci pemasangan yang sah tanpa keanggotaan akan mendapat Not a member of this workspace.
Konsekuensinya bagi isolasi rapi: baris keanggotaan berada di berkas milik ruang kerja itu sendiri, sehingga “siapa saja yang ada di ruang kerja ini?” adalah pembacaan bercakupan yang biasa. Pertanyaan kebalikannya — “saya berada di ruang kerja mana saja?” — menurut definisinya tersebar di banyak berkas, jadi ia termasuk sedikit fan-out CrossWorkspaceQueries yang disahkan, dan itulah yang dijalankan pemilih ruang kerja sebelum ruang kerja mana pun dipilih.
Lihat Multiplayer untuk tangga peran dan pemberian izin repo.
Registri global
Bagian berjudul “Registri global”Tidak semua hal bercakupan ruang kerja. Basis data kedua, <dataDir>/global.db, memuat hal-hal yang benar-benar berskala server:
- registri ruang kerja, agar pengalih dapat mendaftar setiap ruang kerja tanpa membuka satu pun berkas ruang kerja — dan agar nama, logo, pemilik, urutan manual, serta penanda hapus lunak sebuah ruang kerja berada di luar berkas yang mereka gambarkan;
- identitas: pengguna, preferensi pengguna, dan perangkat yang dipasangkan, karena satu orang adalah satu pengguna di setiap ruang kerja dan perangkat yang dipasangkan tetap bertahan saat sebuah ruang kerja dihapus;
- antrean fleet (worker, pekerjaan, log penempatan), yang penjadwalnya mencocokkan seluruh antrean dengan setiap worker pada tiap tick;
- newsfeed, yang bersifat per pengguna (bukan per ruang kerja) — menambahkan feed akan mengikuti Anda ke setiap ruang kerja;
workspace_routes, indeks pra-autentikasi yang menjawab “ruang kerja mana yang memiliki kunci ini?” untuk titik masuk yang datang hanya bermodal rahasia atau id buram (hash undangan, token webhook, deep link) dan tanpa ruang kerja. Tidak ditemukan di sana berarti not-found; memang sengaja tidak ada pemindaian cadangan;- pengaturan dan identitas berskala pemasangan: id pemasangan, pengaturan tingkat host, koneksi SSO, dan kebijakan tindakan terkelola (pengunci berskala pemasangan yang hanya dapat memperketat pagar pengaman ruang kerja).
Saat boot, hanya berkas ini yang dibuka. Berkas ruang kerja dibuka secara lazy pada sentuhan pertama, sehingga biaya startup tetap datar tak peduli berapa banyak riwayat yang diakumulasi ruang kerja, dan quick_check per berkas dibayar pada pemakaian pertama, bukan di jalur menuju banner siap.
Berkas ruang kerja hanya memuat workspace_meta satu baris untuk identifikasi diri (id-nya, pemasangan yang membuatnya, versi skema saat dibuat, dan kapan). Segala hal deskriptif tentang sebuah ruang kerja berada di registri.
Membuat ruang kerja
Bagian berjudul “Membuat ruang kerja”Membuat ruang kerja melakukan lebih dari sekadar menyisipkan baris registri. Dalam satu operasi, server menstempel pengguna pembuat sebagai ownerUserId, mencatat keanggotaan peran owner pengguna itu, dan menerbitkan peristiwa domain WorkspaceCreated. Pendengar peristiwa itu kemudian menanam, secara idempoten dan di latar belakang:
- seorang agen CEO plus empat spesialis —
qa,architect,engineer,librarian— dengan para spesialis melapor kepada CEO; - templat pipeline bawaan beserta pemicunya (pemicu peristiwa dan manual langsung aktif sejak awal; yang terjadwal bersifat opt-in kecuali sapuan GC worktree harian);
- suite eval pemula.
Ini terjadi pada setiap ruang kerja, bukan hanya yang pertama. Agen hasil seeding dibuat tanpa adapter, sehingga mereka berjalan di Control Center (built-in) dengan model default Anthropic (claude-opus-4-8 kecuali Anda mematok model lain) sampai Anda menetapkan adapter dan model per agen di Pengaturan → Ruang kerja → Agen. Lihat Model agen.
Penulisan keanggotaan owner lebih penting daripada kelihatannya: tanpanya, ruang kerja yang baru dibuat tidak akan punya anggota dan setiap panggilan bercakupan ruang kerja yang dilakukan pembuatnya berikutnya akan ditolak.
Menghapus, mengekspor, mengimpor
Bagian berjudul “Menghapus, mengekspor, mengimpor”Menghapus ruang kerja menandai baris registri dan memutus tautan direktorinya. Ruang kerja itu hilang dari setiap daftar dan pencarian. Baris registrinya tetap ada (terhapus lunak) agar id-nya masih diketahui oleh pencadangan dan pemeliharaan, tetapi workspace.delete segera membuang direktori ruang kerja tersebut — basis data, worktree, berkas agen, dan sisanya — melalui WorkspaceDatabaseManager.dropAndClose. Hanya owner ruang kerja yang dapat menghapusnya.
Karena satu ruang kerja adalah satu berkas, memindahkan ruang kerja adalah operasi berkas dan bukan pembuangan tabel demi tabel:
workspace.exportadalah satuVACUUM INTO— snapshot yang konsisten dan terdefragmentasi, diambil di dalam transaksi baca sementara server terus melayani.workspace.importmengadopsi berkas semacam itu, menggantikan apa pun yang saat ini dipegang ruang kerja tujuan.workspace_metayang tertanam memungkinkan sebuah impor membedakan “berkas saya sendiri, diadopsi kembali” dari “berkas dari pemasangan lain” (yang terakhir diizinkan, tetapi dicatat, bukan dibiarkan tak terlihat).server.backupNowmembuat snapshot seluruh pemasangan ke dalam direktori bertanda waktu yang mencerminkan data dir aktif —manifest.json,global.db, dan satu<workspaceId>/workspace.dbper ruang kerja — sehingga pemulihannya adalah menyalinnya kembali.server.listBackupsmembaca kembali direktori itu, dari yang terbaru, beserta ukuran dan daftar ruang kerja tiap snapshot. Snapshot yang manifest-nya hilang atau menyebut berkas yang tidak ada tetap dilaporkan, ditandai tidak lengkap — menyembunyikannya justru membuat operator percaya memiliki cadangan yang sebenarnya tidak ada.
Operasi pencadangan berada di bawah Pengaturan → Server → Cadangan & pemulihan: Cadangkan sekarang mengambil snapshot pemasangan, daftarnya menunjukkan snapshot mana yang lengkap dan memungkinkan pemilik pemasangan menghapusnya setelah konfirmasi, dan setiap ruang kerja menawarkan ekspor dan impor. Pipeline bawaan Workspace backup hanya mengambil basis data ruang kerja itu dengan jadwal mingguan dan menghapus cadangannya sendiri yang lebih tua dari usia retensi yang dikonfigurasi. Memulihkan sebuah ruang kerja dari snapshot pemasangan berarti workspace.import yang diarahkan ke <workspaceId>/workspace.db milik snapshot; memulihkan seluruh pemasangan tetap berupa penyalinan kembali direktori snapshot dengan server dihentikan.
Yang tidak dibawa oleh satu pun dari operasi itu adalah sisa direktori ruang kerja. Ruang kerja mengakumulasi lebih dari sekadar basis data — gambar yang ditempel, berkas skill dan agen, kredensial chat, worktree — dan ekspor serta impor memindahkan berkas itu, sehingga semua yang ada di sampingnya tetap di tempatnya. Itu memang disengaja untuk kredensial dan dapat dibuang untuk worktree; untuk sisanya, ini adalah batasan yang perlu diketahui, bukan sebuah fitur. Referensi cadangan memuat tabel lengkapnya.
Memindahkan cadangan antara server dan perangkat Anda
Bagian berjudul “Memindahkan cadangan antara server dan perangkat Anda”Setiap op di atas berbicara dalam jalur di server, yang merupakan jawaban utuh hanya jika server itu adalah mesin Anda sendiri. Tiga rute HTTP bertanda tangan — /backup/workspace, /backup/snapshot dan /backup/restore — membawa byte untuk semua topologi lainnya, di jalur terautentikasi yang sama yang sudah dipakai proksi media dan penyimpan gambar. Di halaman, ini berupa tombol Unduh di samping setiap ekspor dan setiap snapshot (Anda memilih tempat penyimpanannya), dan Pilih file lalu unggah untuk memulihkan dari berkas yang berada di mesin yang Anda pakai. Unggahan inilah yang berfungsi saat server bukan mesin Anda: workspace.import memerlukan berkasnya sudah ada di host, dan beginilah cara berkas itu sampai ke sana.
Ada tiga sifat yang perlu diketahui:
- Unduhan membuat salinan baru dan server tidak menyimpan satu pun. Mengunduh bukan berarti “mengambil berkas yang dibuat tombol ekspor” — ia mengekspor ulang, menyalurkan hasilnya, lalu menghapusnya. Responsnya
no-storedenganAccept-Ranges: nonekarena alasan yang sama: rentang yang dilanjutkan akan menyambung dua ekspor berbeda menjadi satu berkas yang terlihat valid padahal tidak. - Unggahan disalurkan ke disk, tidak pernah ditampung di memori, di kedua ujungnya, dan salinan yang di-staging dihapus pada setiap jalur keluar — termasuk saat ditolak. Basis data ruang kerja bukanlah tangkapan layar.
- Koneksi yang direlai tidak memiliki origin HTTP, sehingga kontrol transfer dinonaktifkan di sana disertai catatan yang menjelaskan alasannya. Semua hal yang berbicara dalam jalur sisi server tetap berfungsi lewat relay; hanya jalur byte yang memerlukan koneksi langsung.
Beberapa ruang kerja
Bagian berjudul “Beberapa ruang kerja”Anda dapat menjalankan beberapa ruang kerja secara berdampingan. Masing-masing beroperasi secara independen: agen di satu ruang kerja tidak dapat mengirim pesan ke agen di ruang kerja lain, tidak dapat membaca memori ruang kerja lain, dan tidak dapat melihat tiketnya. Resolusi penerima untuk perpesanan antar-agen bersifat persis dan tidak pernah melintasi ruang kerja.
Tidak ada dasbor lintas ruang kerja. Permukaan analitik di /workspaces/:workspaceId/observability berlangganan feed global terbatas berisi run terbaru lalu langsung mempersempitnya ke ruang kerja aktif, sehingga yang Anda lihat di sana adalah satu ruang kerja pada satu waktu. Satu-satunya permukaan yang benar-benar multi-ruang kerja yang disentuh seseorang adalah pemilih ruang kerja dan pengalihnya.
Satu batasan praktis yang perlu diketahui: ruang kerja yang sedang digunakan seseorang tetap terbuka — tidak ada LRU. Setelah melewati 32 ruang kerja seperti itu, server memberi peringatan alih-alih menolak, karena setiap berkas yang terbuka menahan isolate latar belakang dan cache halaman. Fan-out lintas ruang kerja membuka berkas secara sementara lalu menutupnya kembali jika tidak ada hal lain yang mengklaimnya.
Konsep terkait
Bagian berjudul “Konsep terkait”- Multiplayer: keanggotaan, peran, dan pemberian izin repo — separuh akses dari isolasi
- Model agen: agen tergolong tepat pada satu ruang kerja
- Memori dan pengetahuan: memori bercakupan ruang kerja
- Arsitektur: di mana basis data berada dalam pemisahan klien/server
- Cadangkan dan pulihkan: mengambil snapshot, memindahkan ruang kerja, menyimpan salinan di tempat lain
- Cadangan, ekspor, dan impor: setiap operasi, rute, bidang manifest, dan batas
- Tambahkan repo ke ruang kerja: mendaftarkan checkout ke sebuah ruang kerja