Przejdź do głównej zawartości

Wieloosobowość — tożsamość, członkostwo i obecność

Control Center działa dla jednego operatora, ale model pod spodem od samego początku zakłada wielu aktorów. Ludzie i agenci piszą w tych samych przestrzeniach, edytują te same zgłoszenia i pojawiają się na tej samej liście. Aby było to bezpieczne i zrozumiałe, trzeba rozstrzygnąć trzy kwestie: kto liczy się jako aktor, co warunkuje dostęp oraz który stan jest współdzielony trwale, a który tylko rozgłaszany na żywo.

Podmioty: użytkownicy i agenci to równorzędni aktorzy

Dział zatytułowany „Podmioty: użytkownicy i agenci to równorzędni aktorzy”

Wszystko, co zapisuje „kto to zrobił” — wiadomości, zgłoszenia, recenzje, plany, dzienniki uruchomień, wpisy audytu — przechodzi przez Podmiot, zapieczętowaną unię UserPrincipal i AgentPrincipal o zwartej postaci transmisyjnej (user:<id> / agent:<id>). Przypisanie nigdy nie sprowadza N ludzi (ani N agentów) do jednego konta zastępczego, więc recenzję pozostawioną przez agenta i recenzję pozostawioną przez człowieka można odróżnić wszędzie, gdzie się pojawiają.

Ponieważ podmiot jest jednostką tożsamości, jest też jednostką izolacji:

  • Limity szybkości obowiązują na podmiot, nie na sesję. Zabezpieczenie przed zalaniem dla zdalnych wywołań narzędzi rozlicza limit według użytkownika, a nie według połączenia: wszystkie urządzenia członka czerpią z jednego wspólnego przesuwnego okna (120 wywołań na minutę, z czego najwyżej 30 może coś zmienić), więc otwarcie trzech urządzeń nie potraja niczyjego przydziału, a jeden hałaśliwy członek nie odbierze przydziału pozostałym.
  • Cofanie działa na podmiot. Dziennik działań każdego klienta rejestruje wyłącznie modyfikacje wykonane przez jego podmiot. Własną zmianę zgłoszenia cofniesz przez ⌘Z; zmiany współpracownika nie cofniesz — zanim ją zobaczysz, tor trwały już ją rozstrzygnął (patrz poniżej).

Członkostwo to granica dostępu — nie klucz parowania

Dział zatytułowany „Członkostwo to granica dostępu — nie klucz parowania”

Dwa osobne poświadczenia odpowiadają na dwa osobne pytania. Klucz parowania (patrz Zdalne sterowanie i mobile) uwierzytelnia urządzenie wobec serwera. Członkostwo upoważnia użytkownika tego urządzenia do działania w obszarze roboczym. Sparowane urządzenie, którego użytkownik nie ma członkostwa w obszarze roboczym, nie może go ani odczytać, ani zmienić.

Członkostwo to jeden wiersz na parę (obszar roboczy, użytkownik), przechowywany w własnym pliku bazy danych tego obszaru roboczego — dzięki temu pytanie „kto jest w tym obszarze roboczym?” to zwykły odczyt w zakresie, a pytanie odwrotne, „w których obszarach roboczych jestem?”, z konieczności obejmuje wiele plików i jest jednym z nielicznych sankcjonowanych rozproszeń między obszarami roboczymi. To ono uruchamia się w selektorze obszarów roboczych, zanim zostanie wybrany jakikolwiek obszar.

Każdy wiersz niesie rolę z pięciostopniowej, uporządkowanej rangowo drabinki:

Rola Może
owner Pełna kontrola, w tym usuwanie obszaru roboczego; dokładnie jeden na obszar roboczy
admin Zarządzanie członkami, zaproszeniami, ustawieniami, operacjami niszczącymi
member Codzienny odczyt i zapis: wiadomości, zgłoszenia, uruchomienia agentów
viewer Tylko odczyt w całym obszarze roboczym; wzorce wykluczania sekretów ukrywają pasujące ścieżki na powierzchniach z kodem
guest Ten sam minimalny poziom tylko do odczytu co viewer, na najniższym szczeblu drabinki

Każda decyzja o dostępie sprowadza się do pytania „czy rola wywołującego to co najmniej X”. Minimalny poziom wynika z rodzaju operacji, chyba że operacja zadeklaruje go wprost: odczyt wymaga guest, modyfikacja wymaga member, operacja niszcząca wymaga admin. ACL-e na poszczególnych obiektach poza grantami na repozytoria opisanymi poniżej są celowo poza zakresem.

Zaproszenia to sposób przyznawania członkostwa, w Ustawienia → Obszar roboczy → Członkowie. Administrator generuje jednorazowy kod (jako link lub kod QR), który domyślnie wygasa po siedmiu dniach; serwer przechowuje wyłącznie skrót SHA-256 kodu, a sam kod pokazuje się dokładnie raz. Wykorzystanie go tworzy użytkownika w trybie JIT, zapisuje członkostwo na poziomie roli z zaproszenia (nigdy owner — własność przekazuje się jawnie, nigdy przez zaproszenie) i przekazuje dalej do parowania urządzenia. Poświadczenia urządzenia utworzone po sparowaniu same mają limit 30 dni, więc sparowanie nie jest kluczem na stałe.

Granty na repozytoria leżą poniżej członkostwa. Ponieważ serwer przechowuje pełne kopie robocze, samo członkostwo w obszarze roboczym nie może dawać większych uprawnień niż host kodu: każda powierzchnia z kodem — pliki, diffy, recenzja PR, wyszukiwanie kodu — sprawdza grant członka na tym repozytorium. Granty są przyznawane na trójkę (obszar roboczy, użytkownik, repozytorium) i mają rangi none → read → review → write, gdzie write oznacza prowadzenie uruchomień agentów, które zapisują do repozytorium. Brak wiersza oznacza none; właściciele i administratorzy domyślnie mają write na każdym połączonym repozytorium i nie mają żadnych wierszy.

Control Center rozdziela stan współdzielony na dwa tory o przeciwnych zasadach trwałości, ponieważ oba rodzaje stanu zawodzą w przeciwny sposób.

Obecność — kto jest online, gdzie jest, co pisze i co agent robi właśnie teraz — traci wartość w chwili, gdy staje się nieaktualna. Gdyby była utrwalana, każde ponowne połączenie musiałoby uzgadniać „baza danych mówi, że Sam edytuje to zgłoszenie” z rzeczywistością. Dlatego obecność nigdy nie trafia do bazy danych. Żyje w działającym w pamięci hubie obecności na serwerze: klienci publikują przez presence.update, inni subskrybują przez presence.watch, a wpisy wygasają automatycznie po trzech pominiętych sygnałach życia (sygnały życia biegną co 10 sekund; hub czyści co 5). Nie ma czego uzgadniać — można tylko pozwolić wpisom wygasnąć.

Dwie właściwości czynią ten tor godnym zaufania:

  • Tożsamość pochodzi z sesji, nigdy z argumentów klienta. Serwer mapuje uwierzytelnione urządzenie połączenia na identyfikator użytkownika; klient może aktualizować wyłącznie własną obecność, więc nikt nie podszyje się pod status ani lokalizację innego uczestnika. Jawna aktualizacja offline (rozłączenie lub przełącznik trybu niewidocznego) usuwa wpis natychmiast.
  • Lista uczestników jest obsługiwana przez hub serwera. Uczestnicy nigdy nie wymieniają obecności bezpośrednio, więc każdy widzi tę samą listę niezależnie od topologii. Agenci nie mają klienta, więc serwer syntetyzuje ich wpisy na podstawie bieżącego stanu uruchomień — myśli, działa, czeka zablokowany na bramce zatwierdzenia, gotowe — przy czym stan i lokalizację agenta wyznacza jedno reprezentatywne uruchomienie, aby równoległe uruchomienia nie powodowały migotania listy.

Rozprowadzanie ma poziomy zależne od odbiorcy: pulpit i sieć jadą na pełnym poziomie (co najwyżej jedno rozgłoszenie listy na 100 ms), a telefon na poziomie skróconym (co 500 ms), dzięki czemu aktualizacje w tempie kursora nie roztopią PWA.

Lokalizacja uczestnika — gdzie aktualnie jest, aż do konkretnego artefaktu — to zapieczętowana unia: Space, File (repozytorium + ścieżka + opcjonalny wiersz), Pr, Ticket, PlanNode. Nie każda lokalizacja ma dokąd przenieść: nie istnieje trasa podglądu plików, więc lokalizacja File renderuje się jako etykieta, a nie cel nawigacji. Tor przenosi też miękkie zastrzeżenia („Sam edytuje to zgłoszenie”, „agent zajmuje to worktree”): widoczność konfliktu, a nie blokady. Nieznane rodzaje lokalizacji są parsowane do null, więc stary klient po prostu nie renderuje tego, czego nie rozumie.

Zapisy, które muszą przetrwać — zgłoszenia, wspólny dokument notatek przestrzeni, wiadomości — idą przeciwną drogą: są autorytatywne, uporządkowane i utrwalane. Równoczesne zmiany rozstrzyga ostatni zapis, w kolejności odbioru przez serwer, nigdy zegary klientów. Rozstrzygnięcie jest celowo drobnoziarniste: zmiany w zgłoszeniu są osobne dla każdej kolumny, więc gdy dwie osoby edytują różne pola tego samego zgłoszenia, obie zmiany przechodzą i żadna nie nadpisuje drugiej (przejścia przepływu zachowują natomiast blokadę optymistyczną); dokument notatek przestrzeni to jedna kolumna, więc cały dokument podlega regule ostatniego zapisu, a miękkie zastrzeżenia w torze obecności czynią równoczesną edycję widoczną, zamiast ją blokować. Celowo nie jest to CRDT — ciężar dowodu spoczywa na CRDT, a ostatni zapis rozstrzygany kolejnością odbioru plus widoczne zastrzeżenia pokrywają rzeczywiste wzorce współpracy.

„Ostatni zapisujący” oznacza kolejność odbioru przez serwer, nigdy zegar klienta — laptop z przekrzywionym zegarem nie wygra edycji, którą przegrał. Za torem trwałym stoi strumień synchronizacji: wyzwalacze SQLite dopisują każdą modyfikację do dziennika zmian danego obszaru roboczego wewnątrz transakcji zapisującej, przydzielając monotoniczny numer sekwencji obszaru roboczego w tej samej transakcji co sama modyfikacja. Usługa serwera śledzi ten dziennik i zamienia go na wersjonowane ramki różnicowe w kolejności sekwencji. Klienci przechodzą od pełnych migawek do różnic; luka w sekwencji cofa ten magazyn do trybu migawek, zamiast błędnie stosować ramki.

Tor obecności to nie tylko lista — napędza czasowniki współpracy:

  • Tryb podążania. Kliknij uczestnika, a twój klient jedzie za jego lokalizacją, nawigując tak jak on. Podążanie trwa „do twojego pierwszego działania”: w chwili, gdy zaczniesz nawigować samodzielnie, podążanie się odłącza. Zamykalny baner informuje, gdy inny uczestnik pokazuje na pierwszym planie (prezentuje) przestrzeń, w której jesteś.
  • Kierowanie. Wiadomość do działającego agenta trafia do kolejki jako wskazówka sterująca; agent odczytuje ją ponownie przed wznowieniem kolejnej tury, więc możesz podprowadzić uruchomienie w locie, nie zabijając go.
  • Przejęcie. Przejęcie kontroli nad rozmową wstrzymuje każde aktywne uruchomienie na jego następnej czystej granicy tury (uruchomienie Claude Code, które nie ma bezpiecznej granicy, zostaje zatrzymane) za migawką git z każdej tury, dzięki czemu przejęcie jest odwracalne. Trwały znacznik blokuje nowe wysyłki do przestrzeni i przetrwa restart serwera — serwer nigdy nie wznawia automatycznie pracy w na wpół dokończonej edycji człowieka. Jeden aktywny edytor na worktree to miękkie zastrzeżenie: drugie przejęcie jest widoczne i odrzucane, a nie po cichu scalane.
  • Oddanie. Zwrócenie kontroli publikuje w przestrzeni ustrukturyzowane podsumowanie diffu worktree, umieszcza je w kolejce jako wskazówkę sterującą dla wstrzymanych uruchomień, wznawia je i czyści znacznik.

Kierowanie i przejmowanie to interwencje po fakcie. Pokrętło autonomii to ustawienie stałe: osobne dla przestrzeni i dla agenta, ustawiane z nagłówka przestrzeni; decyduje o tym, co się dzieje, gdy agent dotrze do bramkowanego działania.

Poziom Wartość transmisyjna Wpływ na bramkowane wywołanie narzędzia
Tylko propozycje proposeOnly Odmawiane wprost, z uzasadnieniem, które agent ma przekazać — może opisać działanie, ale nie może go wykonać
Działa po zatwierdzeniu actWithApproval Bramka zatwierdzenia domyślnie odmawiająca: jedno potwierdzenie trafia do połączonego operatora, a brak zatwierdzającego oznacza odmowę
Działa swobodnie actFreely Wstępnie zatwierdzone, z wyjątkiem sytuacji, gdy ma zastosowanie twarda reguła deny z guardraili — reguła odmowy zawsze wygrywa
(nieustawione) null To samo co działanie po zatwierdzeniu: ustawienie domyślne serwera

Dwie rzeczy znajdują się poza pokrętłem w obu kierunkach. Własny czasownik wyjściowy trybu jest przypięty i nigdy nie może zostać odrzucony, niezależnie od tego, co mówi pokrętło — tryb orkiestracji przez pewien czas był strukturalnie niezdolny do wyemitowania swojego jedynego rezultatu, bo preset odmawiał klasie efektów, którą deklaruje ten czasownik. A twardej odmowy deny z guardraili działań nie może uchylić actFreely.

Wszystko to jest dodatkiem. Przepływ pracy pojedynczego operatora się nie zmienia: przy jednym człowieku i bez agentów hub obecności nie ma czego rozgłaszać, więc tor emituje początkową migawkę, a potem pozostaje bezczynny przy zerowym ruchu; mechanika ostatniego zapisu i synchronizacji to ta sama ścieżka jednego piszącego, co zawsze; a klucz parowania pozostaje poświadczeniem urządzenia, którym zawsze był. Członkostwo jest już granicą dostępu — przy jednym człowieku istnieje jeden wiersz właściciela i żadnych elementów listy.