Hoppa till innehåll

Arbetsytor och isolering

En arbetsyta är den isolerande tenanten på toppnivå i Control Center. Den grupperar agenter, repos, ytor, ärenden, minne, pipelines och medlemmar i en avgränsad kontext. Allt som händer inuti en arbetsyta stannar där.

En typisk arbetsyta motsvarar ett projekt eller en kodbas. Du kan ha separata arbetsytor för ”api-service”, ”frontend-v2” och ”infrastructure”.

En arbetsytas rader ligger i arbetsytans egen SQLite-fil, på <dataDir>/<workspaceId>/workspace.db. Varje arbetsyta får en katalog i stället för en bar fil, eftersom en arbetsyta samlar mer än en databas — worktrees per samtal, agent- och skillfilerna, chattbotens autentiseringsuppgifter — och att ge den en mapp gör att allt det lever och raderas tillsammans.

Entitet Omfång
Agenter Tillhör exakt en arbetsyta
Repos Registreras i en arbetsyta (se nedan)
Ytor, samtal och meddelanden Arbetsytespecifika
Ärenden, projekt, planer, orkestreringar Arbetsytespecifika
Minnesfakta, policyer, domäner, åtkomstbeviljanden Arbetsytespecifika
Pipelinekörningar, mallar, triggers Arbetsytespecifika
Pull request-status, granskningsytor, granskningskohorter Arbetsytespecifika
Agentkörningsloggar och arbetsminne Arbetsytespecifika
Medlemmar, inbjudningar, beviljanden per repo Arbetsytespecifika
Kodgrafssymboler och kanter Avgränsas av repoId i arbetsytans fil

De flesta av de här raderna bär en workspaceId-kolumn; separata databasfiler upprätthåller isoleringen. Se isoleringsinvarianten nedan.

Repos hör till arbetsytan; identiteten mellan arbetsytor går via sökvägen

Avsnitt med rubriken “Repos hör till arbetsytan; identiteten mellan arbetsytor går via sökvägen”

En repo-rad ligger i arbetsytans egen databasfil — filen är omfånget, så tabellen har ingen workspaceId-kolumn och ingen kopplingstabell.

Du lägger till en repo i en arbetsyta under Inställningar → Arbetsyta → Arkiv. Att registrera samma utcheckning i två arbetsytor skapar två oberoende rader med två id:n; en repos identitet mellan arbetsytor går via filsystemets sökväg (eller GitHub-ägare/namn), aldrig via id.

Registreringen är strikt: sökvägen måste ligga i ett git-arbetsträd, den måste ha en origin-remote och den remoten måste peka på en kodvärd som stöds (github.com, gitlab.com eller bitbucket.org). Kodvärden läses från remoten och lagras på repon, så en arbetsyta kan hålla repos från alla tre. Allt annat avvisas. Mappbläddraren du väljer ur visar servervärdens filsystem, begränsat till serverns --repo-roots-tillåtelselista (som som standard är serveranvändarens hemkatalog).

Att ta bort en repo från en arbetsyta raderar raden helt och kaskaderar dess kodsymboler, kanter, filer, indexkontrollpunkter och beviljanden per medlem. Utcheckningen på disken lämnas orörd.

Kodgrafen ligger i samma fil, nycklad på repoId, eftersom olika arbetsytor kan ha samma repo på olika grenar med olika kod.

Läckage av data mellan arbetsytor betraktas som buggar. Isoleringen är strukturell och upprätthålls på flera nivåer:

  • Databaslagret. Varje arbetsyta har sin egen SQLite-fil, som delas ut av WorkspaceDatabaseManager.of(workspaceId). En arbetsytedatabas deklarerar inte users, workspaces eller någon annan arbetsytas tabeller, så en läsning mellan arbetsytor är inte en bugg du kan skriva — den kompilerar inte. Repos löser upp sin DAO per anrop i stället för att cacha en, och eftersom varje repometod tar emot en obligatorisk workspaceId kan arbetsytan aldrig härledas eller antas som standard.
  • Arbetsytans id är ett sökvägssegment. Eftersom id:t blir ett katalognamn måste det matcha ett säkert mönster för ett enda segment (inledande alfanumeriskt tecken, därefter alfanumeriska tecken, punkter, understreck, bindestreck; inga ..). Id:n är UUID:er överallt i produkten. Allt annat kastar i stället för att saneras — det är skyddet mot sökvägstraversering, och det misslyckas högljutt i stället för att skriva en arbetsytas data till en sökväg som någon annan har valt.
  • Försvar på djupet. Kolumnerna workspaceId finns fortfarande kvar och skrivs fortfarande. Överflödiga inom en fil håller de synkflödets triggers, FTS-index och befintliga radformer oförändrade, och de gör en fil självbeskrivande när den granskas på egen hand — vilket är vad export och import förlitar sig på.
  • Domänlagret. Tjänster som muterar entiteter via id validerar entity.workspaceId == workspaceId vid en enda kontrollpunkt innan de fortsätter och kastar WorkspaceMismatchException vid avvikelse — det högljudda misslyckandet för ett id som kommit från fel kontext. Att neka högljutt spelar roll: en tyst no-op döljer buggen och att fortsätta läcker.
  • RPC-lagret. Repo-RPC-dispatcharen är kontrollpunkten. Varje arbetsytespecifik operation måste bära workspace_id i sina egna argument — det finns ingen ”aktuell arbetsyta” per session att läcka, och ett okänt id avvisas som not-found innan något öppnar en databas, så ett dåligt id kan inte materialisera en spökdatabas workspace.db. Operationer som verkligen är globala (nyhetsflödet, fleet-kön) deklarerar sig som utan omfång genom ett uttryckligt, granskat beslut. MCP-verktyg följer samma regel: varje verktyg som rör arbetsytespecifik data kräver workspace_id.
  • Fan-out mellan arbetsytor går att räkna upp. Att svara på en fråga om flera arbetsytor innebär att öppna flera databaser. Den fan-outen är begränsad till en enda hjälpare, CrossWorkspaceQueries, så den fullständiga listan över saker som legitimt korsar gränsen finns på en fils anropsställen i stället för att spridas bakom dokumentationskommentarer. De anropsställena är: aggregering över hela servern, avstämningar vid start, kvarhållnings- och GC-svep, säkerhetskopiering, händelseroutrar — samt medlemskapsuppslagningen nedan.
  • Åtkomst enbart via id räcker inte. Att slå upp en entitet via sitt UUID bevisar inte att den tillhör anroparens arbetsyta. Filuppdelningen gör fel fil onåbar; valideringen i domänlagret fångar ett felaktigt id som kommer in via en legitim fil.

Medlemskapet, inte parkopplingsnyckeln, ger åtkomsten

Avsnitt med rubriken “Medlemskapet, inte parkopplingsnyckeln, ger åtkomsten”

Att inneha en enhetsautentiseringsuppgift autentiserar en enhet. Den auktoriserar ingenting inuti en arbetsyta. Varje arbetsytespecifik operation löser upp anroparens medlemskapsroll och upprätthåller en nedre gräns som härleds från operationens slag — läsning kräver guest, en mutation kräver member, en destruktiv operation kräver admin — och operationer som exponerar repo-innehåll kontrollerar dessutom anroparens beviljande per repo. En giltig parkopplingsnyckel utan medlemskap får Not a member of this workspace.

Konsekvensen för isoleringen är prydlig: medlemskapsraderna ligger i arbetsytans egen fil, så ”vilka finns i den här arbetsytan?” är en vanlig läsning med omfång. Den omvända frågan — ”vilka arbetsytor är jag med i?” — är per definition spridd över flera filer, så den är en av de få sanktionerade fan-outarna via CrossWorkspaceQueries, och det är vad arbetsyteväljaren kör innan någon arbetsyta har valts.

Se Multiplayer för rolltrappan och beviljandena per repo.

Inte allt har arbetsyteomfång. En andra databas, <dataDir>/global.db, håller det som verkligen gäller hela servern:

  • arbetsyteregistret, så att växlaren kan lista varje arbetsyta utan att öppna en enda arbetsytefil — och så att en arbetsytas namn, logotyp, ägare, manuella ordning och mjukraderingsmarkör ligger utanför filen de beskriver;
  • identitet: användare, användarinställningar och parkopplade enheter, eftersom en människa är en användare i varje arbetsyta och en parkopplad enhet överlever att en arbetsyta raderas;
  • fleet-kön (workers, jobb, placeringslogg), vars schemaläggare matchar hela kön mot varje worker vid varje tick;
  • nyhetsflödet, som är per användare (inte per arbetsyta) — ett flöde du lägger till följer med dig in i varje arbetsyta;
  • workspace_routes, indexet före autentisering som svarar på ”vilken arbetsyta äger den här nyckeln?” för ingångar som anländer med inget annat än en hemlighet eller ett ogenomskinligt id (en inbjudningshash, en webhook-token, en djuplänk) och ingen arbetsyta. En miss där är en not-found; det finns medvetet ingen skanning som reserv;
  • installationstäckande inställningar och identitet: installations-id:t, inställningar på värdnivå, SSO-anslutningar och hanterade åtgärdspolicyer (en installationstäckande spärr som bara kan skärpa arbetsytornas räcken).

Vid start öppnas bara den här filen. Arbetsytefiler öppnas först vid första åtkomst, så startkostnaden förblir konstant oavsett hur mycket historik arbetsytorna samlar på sig, och quick_check per fil betalas vid första användning i stället för på vägen fram till redo-bannern.

En arbetsytefil bär bara en enkelradig workspace_meta för självidentifiering (dess id, installationen som skapade den, schemaversionen den skapades med och när). Allt beskrivande om en arbetsyta ligger i registret.

Att skapa en arbetsyta gör mer än att infoga en rad i registret. I en och samma operation stämplar servern den skapande användaren som ownerUserId, registrerar den användarens medlemskap med ägarrollen och publicerar en domänhändelse WorkspaceCreated. Lyssnare på den händelsen sår sedan, idempotent och i bakgrunden:

  • en CEO-agent plus fyra specialister — qa, architect, engineer, librarian — där specialisterna rapporterar till CEO-agenten;
  • de inbyggda pipelinemallarna och deras triggers (händelse- och manuella triggers levereras aktiverade; schemalagda är opt-in utom det dagliga worktree-GC-svepet);
  • start-evalsviterna.

Det här sker för varje arbetsyta, inte bara den första. De sådda agenterna skapas utan adapter, så de kör på Control Center (built-in) med Anthropics standardmodell (claude-opus-4-8 om du inte låser en annan) tills du ställer in en adapter och modell per agent under Inställningar → Arbetsyta → Agenter. Se Agentmodellen.

Skrivningen av ägarmedlemskapet betyder mer än den ser ut: utan den skulle den ny skapade arbetsytan inte ha några medlemmar, och varje arbetsytespecifikt anrop skaparen gör därefter skulle nekas.

Att radera en arbetsyta markerar registerraden och avlänkar katalogen. Arbetsytan försvinner från varje lista och uppslagning. Registerraden ligger kvar (mjukraderad) så att id:t fortfarande är känt för säkerhetskopiering och underhåll, men workspace.delete släpper omedelbart den arbetsytans katalog — databas, worktrees, agentfiler och resten — via WorkspaceDatabaseManager.dropAndClose. Bara arbetsytans ägare kan radera den.

Eftersom en arbetsyta är en fil är det en filoperation att flytta en arbetsyta mellan händer, inte en tabell-för-tabell-dump:

  • workspace.export är en enda VACUUM INTO — en konsekvent, defragmenterad ögonblicksbild tagen inom en lästransaktion medan servern fortsätter betjäna.
  • workspace.import tar över en sådan fil och ersätter allt målarbetsytan för närvarande håller. Den inbäddade workspace_meta låter en import skilja ”min egen fil, återigen övertagen” från ”en fil från en annan installation” (den senare är tillåten, men loggas i stället för att förbli osynlig).
  • server.backupNow tar en ögonblicksbild av hela installationen till en tidsstämplad katalog som speglar den aktiva datakatalogen — manifest.json, global.db och en <workspaceId>/workspace.db per arbetsyta — så att återställning är att kopiera tillbaka den.
  • server.listBackups läser tillbaka den katalogen, senaste först, med storlek och arbetsytelista för varje ögonblicksbild. En ögonblicksbild vars manifest saknas eller namnger filer som inte finns rapporteras fortfarande, flaggad som ofullständig — att dölja den är hur en operatör börjar tro att de har en säkerhetskopia de inte har.

Säkerhetskopieringsoperationerna ligger under Inställningar → Server → Säkerhetskopiering och återställning: Säkerhetskopiera nu tar en ögonblicksbild av installationen, listan visar vilka ögonblicksbilder som är kompletta och låter installationens ägare radera en efter bekräftelse, och varje arbetsyta erbjuder export och import. Den inbyggda pipelinen Workspace backup tar bara den arbetsytans databas enligt ett veckoschema och raderar sina egna säkerhetskopior som är äldre än den konfigurerade kvarhållningsåldern. Att återställa en arbetsyta från en installationsögonblicksbild är workspace.import riktad mot ögonblicksbildens <workspaceId>/workspace.db; att återställa en hel installation är fortfarande en tillbakakopiering av ögonblicksbildskatalogen med servern stoppad.

Det ingen av dem bär med sig är resten av arbetsytekatalogen. En arbetsyta samlar mer än en databas — inklistrade bilder, skill- och agentfiler, chattautentiseringsuppgifter, worktrees — och export och import flyttar filen, så allt bredvid den stannar där det är. Det är avsiktligt för autentiseringsuppgifter och förbrukningsbart för worktrees; för resten är det en gräns att känna till snarare än en funktion. Referensen för säkerhetskopia har hela tabellen.

Flytta en säkerhetskopia mellan servern och din enhet

Avsnitt med rubriken “Flytta en säkerhetskopia mellan servern och din enhet”

Varje operation ovan talar i sökvägar på servern, vilket bara är ett fullständigt svar när servern är din egen maskin. Tre signerade HTTP-rutter — /backup/workspace, /backup/snapshot och /backup/restore — bär byten för varje annan topologi, på samma autentiserade bana som medieproxyn och bildlagret redan använder. På sidan är det här en Hämta-knapp bredvid varje export och varje ögonblicksbild (du väljer var den hamnar), och Välj en fil och ladda upp för att återställa från en fil som ligger på maskinen du använder. Uppladdningen är den som fungerar när servern inte är din maskin: workspace.import kräver att filen redan ligger på värden, och det här är hur den tar sig dit.

Tre egenskaper är värda att känna till:

  • En nedladdning skapar en färsk kopia och servern behåller inget av den. Att hämta är inte ”hämta filen som exportknappen skapade” — den exporterar igen, strömmar den och raderar den. Svaret är no-store med Accept-Ranges: none av samma skäl: ett återupptaget intervall skulle skarva ihop två olika exporter till en fil som ser giltig ut men inte är det.
  • En uppladdning strömmas till disk, buffras aldrig, i båda ändar, och den mellanlagrade kopian raderas på varje väg ut — inklusive ett avslag. En arbetsytedatabas är inte en skärmbild.
  • En vidarebefordrad anslutning har inget HTTP-ursprung, så överföringskontrollerna är inaktiverade där med en notis som förklarar varför. Allt som talar i sökvägar på serversidan fortsätter fungera över reläet; bara byte-banorna behöver en direkt anslutning.

Du kan köra flera arbetsytor sida vid sida. Var och en verkar oberoende: agenter i en arbetsyta kan inte skicka meddelanden till agenter i en annan, inte läsa en annan arbetsytas minne och inte se dess ärenden. Mottagaruppslagningen för meddelanden mellan agenter är exakt och korsar aldrig en arbetsyta.

Det finns ingen instrumentpanel över arbetsytor. Analysytan på /workspaces/:workspaceId/observability prenumererar på ett begränsat globalt flöde av senaste körningar och snävar omedelbart in det till den aktiva arbetsytan, så det du ser där är en arbetsyta i taget. De enda ytorna som verkligen omfattar flera arbetsytor och som en människa rör vid är arbetsyteväljaren och växlaren.

En praktisk gräns värd att känna till: en arbetsyta som någon använder förblir öppen — det finns ingen LRU. Bortom 32 sådana varnar servern i stället för att vägra, eftersom varje öppen fil håller en isolate i bakgrunden och en sidcache. Fan-out mellan arbetsytor öppnar filer tillfälligt och stänger dem efteråt om inget annat har tagit dem i anspråk.