Hoppa till innehåll

Multiplayer – identitet, medlemskap och närvaro

Control Center fungerar för en enskild operatör, men modellen under den är multiaktörs från början. Människor och agenter skickar meddelanden i samma ytor, redigerar samma ärenden och syns i samma personallista. Att göra det säkert och begripligt kokar ner till tre beslut: vem som räknas som aktör, vad som grindar åtkomsten och vilket tillstånd som delas varaktigt kontra bara sänds ut live.

Principaler: användare och agenter är jämbördiga aktörer

Avsnitt med rubriken “Principaler: användare och agenter är jämbördiga aktörer”

Allt som registrerar ”vem gjorde detta” – meddelanden, ärenden, granskningar, planer, körningsloggar, revisionsposter – går genom en Principal, en förseglad union av UserPrincipal och AgentPrincipal med en kompakt trådform (user:<id> / agent:<id>). Tillskrivningen slår aldrig samman N människor (eller N agenter) till ett enda sentinelkonto, så en granskning från en agent och en granskning från en person går att skilja åt överallt där de dyker upp.

Eftersom principalen är identitetsenheten är den också inneslutningsenheten:

  • Hastighetsgränser gäller per principal, inte per session. Översvämningsskyddet för fjärranrop till verktyg budgeteras per användare, inte per anslutning: alla en medlems enheter hämtar från ett gemensamt glidande fönster (120 anrop i minuten, varav högst 30 får mutera), så att öppna tre enheter tredubblar inte någons kvot och en stökig medlem kan inte svälta ut de andra.
  • Ångra gäller per principal. Varje klients åtgärdsjournal registrerar bara de mutationer som dess principal utförde. Du kan ⌘Z din egen ärenderedigering; du kan inte ångra en kollegas – när du ser den har den varaktiga banan redan löst den (se nedan).

Medlemskapet är åtkomstgränsen – inte parkopplingsnyckeln

Avsnitt med rubriken “Medlemskapet är åtkomstgränsen – inte parkopplingsnyckeln”

Två separata autentiseringsuppgifter svarar på två separata frågor. Parkopplingsnyckeln (se Fjärrstyrning och mobil) autentiserar en enhet mot servern. Medlemskapet auktoriserar den enhetens användare att agera inuti en arbetsyta. En parkopplad enhet vars användare inte har något medlemskap i en arbetsyta kan varken läsa eller mutera den.

Medlemskapet är en rad per (arbetsyta, användare), lagrad i arbetsytans egen databasfil – så ”vem är med i den här arbetsytan?” är en vanlig avgränsad läsning, medan den omvända frågan, ”vilka arbetsytor är jag med i?”, nödvändigtvis spänner över filer och är en av de få sanktionerade utskickningarna mellan arbetsytor. Det är vad arbetsyteväljaren kör innan någon arbetsyta har valts.

Varje rad bär en roll från en rangordnad stege med fem nivåer:

Roll Får
owner Full kontroll, inklusive borttagning av arbetsytan; exakt en per arbetsyta
admin Hantera medlemmar, inbjudningar, inställningar, destruktiva operationer
member Vardaglig läs- och skrivåtkomst: meddelanden, ärenden, agentkörningar
viewer Skrivskyddad i hela arbetsytan; globmönster för hemlighetsundantag döljer matchande sökvägar på kodbärande ytor
guest Samma skrivskyddade golv som visare, på stegens lägsta rang

Varje åtkomstbeslut kokar ner till ”har anroparens roll minst X”. Golvet härleds från operationens slag om den inte deklarerar ett eget: en läsning kräver guest, en mutation kräver member, en destruktiv operation kräver admin. ACL:er per objekt utöver behörigheterna per repo nedan ligger medvetet utanför omfattningen.

Inbjudningar är hur medlemskap beviljas, från Inställningar → Arbetsyta → Medlemmar. En admin skapar en engångskod (som länk eller QR) som som standard går ut efter sju dagar; servern lagrar bara kodens SHA-256-hash och själva koden visas exakt en gång. Att lösa in den JIT-provisionerar användaren, registrerar medlemskap med inbjudningens roll (aldrig owner – ägarskap överförs uttryckligen och beviljas aldrig via inbjudan) och lämnar över till enhetsparkopplingen. Enhetsuppgifter som skapas efter parkopplingen är själva tidsbegränsade till 30 dagar, så en parkoppling är inte en permanent nyckel.

Repo-behörigheter ligger under medlemskapet. Eftersom servern har fulla utcheckningar får ett arbetsytemedlemskap ensamt aldrig ge högre privilegier än forge-tjänsten: varje kodbärande yta – filer, diffar, PR-granskning, kodsökning – kontrollerar medlemmens behörighet på det repot. Behörigheter är per (arbetsyta, användare, repo) och rangordnas none → read → review → write, där write innebär att driva agentkörningar som skriver till repot. Avsaknad av en rad betyder none; ägare och admins har implicit write på varje länkat repo och bär inga rader alls.

Control Center delar upp delat tillstånd i två banor med motsatta varaktighetsregler, eftersom de två slagen av tillstånd misslyckas på motsatta sätt.

Närvaro – vem som är online, var de är, vad de skriver, vad en agent gör just nu – är värdelös i samma stund den blir inaktuell. Om den bevarades skulle varje återanslutning behöva stämma av ”databasen säger att Sam redigerar det här ärendet” mot verkligheten. Så närvaro skrivs aldrig till databasen. Den lever i en närvarohubb i minnet på servern: klienter publicerar med presence.update, peers prenumererar med presence.watch och poster upphör automatiskt efter tre missade hjärtslag (hjärtslag körs var 10:e sekund; hubben sveper var 5:e). Det finns inget att stämma av – bara att låta förfalla.

Två egenskaper gör banan pålitlig:

  • Identiteten kommer från sessionen, aldrig från klientargument. Servern mappar anslutningens autentiserade enhet till ett användar-id; en klient kan bara uppdatera sin egen närvaro, så ingen kan förfalska en annan deltagares status eller locus. En uttrycklig offline-uppdatering (frånkoppling eller växlingsknappen för osynlig) tar bort posten omedelbart.
  • Närvarolistan hubbas på serversidan. Peers utbyter aldrig närvaro direkt, så varje deltagare ser samma närvarolista oavsett topologi. Agenter har ingen klient, så servern syntetiserar deras poster från livedata om körningar – tänker, kör, blockerad på en godkännandegrind, klar – där en enda representativ körning avgör en agents tillstånd och locus, så att samtidiga körningar inte får närvarolistan att fladdra.

Utskickningen nivåindelas per konsument: skrivbordet och webben ligger på en full nivå (högst ett utskick av närvarolistan per 100 ms), telefonen på en sammanfattningsnivå (per 500 ms), så att uppdateringar i markörens takt inte kan smälta PWA:n.

En deltagares locus – var de befinner sig just nu, ända ner till artefakten – är en förseglad union: Space, File (repo + sökväg + valfri rad), Pr, Ticket, PlanNode. Alla locus har inte någonstans att hoppa till: det finns ingen rutt för filvisaren, så en File-locus renderas som en etikett i stället för ett mål. Banan bär också mjuka anspråk (”Sam redigerar det här ärendet”, ”en agent håller den här worktreen”): konflikt_synlighet_, inte lås. Okända locus-typer parsas till null, så en gammal klient renderar helt enkelt inte det den inte förstår.

Poster som måste överleva – ärenden, ytans delade Anteckningar-dokument, meddelanden – tar den motsatta vägen: auktoritativa, ordnade och bevarade. Samtidiga redigeringar löses med last-writer-wins i serverns mottagningsordning, aldrig av klientklockor. Upplösningen är medvetet finkornig: ärenderedigeringar sker per kolumn, så två personer som redigerar olika fält i samma ärende får båda genomslag och ingen skriver över den andra (arbetsflödesövergångar behåller i stället optimistisk låsning); ytans Anteckningar-dokument är en enda kolumn, så hela dokumentet är LWW, där mjuka anspråk på närvarobanan gör samtidig redigering synlig i stället för låst. Det är medvetet inte en CRDT – bevisbördan ligger på CRDT:n, och LWW i mottagningsordning plus synliga anspråk täcker de faktiska samarbetsmönstren.

”Senaste skribenten” betyder serverns mottagningsordning, aldrig en klientklocka – en laptop med felställd klocka kan inte vinna en redigering som den förlorade. Bakom den varaktiga banan ligger synkflödet: SQLite-triggers lägger till varje mutation i en ändringslogg per arbetsyta inuti skrivtransaktionen och tilldelar ett monotont sekvensnummer per arbetsyta i samma transaktion som själva mutationen. En servertjänst följer den loggen och omvandlar den till versionshanterade deltaramar i sekvensordning. Klienter utvecklas från fullständiga ögonblicksbilder till deltan; en lucka i sekvensen sätter tillbaka den butiken i ögonblicksläge i stället för att felaktigt tillämpa ramar.

Närvarobanan är inte bara en närvarolista – den driver samarbetsverben:

  • Följläge. Klicka på en deltagare och din klient följer deras locus och navigerar när de navigerar. Följ gäller ”tills du agerar”: i samma stund du navigerar på egen hand kopplas följningen loss. En banderoll som går att stänga meddelar när en annan deltagare spotlightar (presenterar) en yta du är i.
  • Styr. Ett meddelande till en körande agent köas som styrning; agenten läser om det innan den återupptar sin nästa tur, så du kan knuffa en körning medan den pågår utan att döda den.
  • Ta över. Att ta kontrollen över ett samtal pausar varje aktiv körning vid dess nästa rena turgräns (en Claude Code-körning, som saknar säker gräns, stoppas) bakom en git-ögonblicksbild per tur, så att övertagandet går att ångra. En varaktig markör blockerar nya dispatchar in i ytan och överlever en serveromstart – servern återupptar aldrig automatiskt in i en människas halvfärdiga redigering. En levande redigerare per worktree är ett mjukt anspråk: ett andra övertagande syns och avvisas, det slås inte tyst samman.
  • Lämna tillbaka. Att lämna tillbaka kontrollen publicerar en strukturerad diffsammanfattning av worktreen i ytan, köar den som styrning för de pausade körningarna, återupptar dem och rensar markören.

Styrning och övertagande är ingripanden i efterhand. Autonomireglaget är den ständiga inställningen: per yta, per agent, inställd från ytans huvud, och den avgör vad som händer när en agent når en grindad åtgärd.

Nivå Trådvärde Effekt på ett grindat verktygsanrop
Bara föreslå proposeOnly Nekas direkt, med en anledning som agenten uppmanas att vidarebefordra – den kan beskriva åtgärden men inte utföra den
Agera med godkännande actWithApproval Fail-closed-godkännandegrinden: en bekräftelse visas för en ansluten operatör och ingen godkännare betyder nekat
Agera fritt actFreely Förhandsgodkänd, utom där en hård deny-regel bland räckena gäller – en deny-regel vinner alltid
(osatt) null Samma som agera med godkännande: serverns standard

Två saker ligger utanför reglaget i båda riktningarna. Ett läges eget utdataverb är fäst och kan aldrig nekas, vad reglaget än säger – orkestreringsläget kunde en gång i strukturell mening inte sända ut sin enda leverabel eftersom en förinställning nekade den effektklass som verbet deklarerar. Och ett hårt deny från åtgärdsräckena kan inte åsidosättas av actFreely.

Allt detta är additivt. En ensam operatörs arbetsflöde är oförändrat: med en människa och inga agenter har närvarohubben inget att sända ut, så banan skickar sin första ögonblicksbild och står sedan stilla med noll trafik; LWW- och synkmaskineriet är samma enkelskrivarväg som det alltid har varit; och parkopplingsnyckeln förblir den enhetsuppgift den alltid har varit. Medlemskapet är redan åtkomstgränsen – med en människa finns en enda ägarrad och ingen krom för någon närvarolista.