Multigiocatore — identità, appartenenza e presenza
Control Center funziona per un singolo operatore, ma il modello che sta sotto è multi-attore fin dall’inizio. Esseri umani e agenti scrivono negli stessi spazi, modificano gli stessi ticket e compaiono nello stesso elenco. Rendere tutto questo sicuro e comprensibile si riduce a tre decisioni: chi conta come attore, cosa controlla l’accesso e quale stato è condiviso in modo durevole rispetto a quello semplicemente trasmesso in tempo reale.
I Principal: utenti e agenti sono attori alla pari
Sezione intitolata “I Principal: utenti e agenti sono attori alla pari”Tutto ciò che registra «chi ha fatto questo» — messaggi, ticket, revisioni,
piani, log di esecuzione, voci di controllo — si risolve attraverso un
Principal, un’unione sealed di UserPrincipal e AgentPrincipal con una
forma compatta di serializzazione (user:<id> / agent:<id>). L’attribuzione
non collassa mai N persone (o N agenti) in un unico account sentinella, quindi
una revisione lasciata da un agente e una lasciata da una persona restano
distinguibili ovunque emergano.
Poiché il principale è l’unità di identità, è anche l’unità di contenimento:
- I limiti di velocità sono per principale, non per sessione. La protezione anti-flood sulle chiamate remote agli strumenti calcola il budget per utente, non per connessione: tutti i dispositivi di un membro attingono da un’unica finestra scorrevole condivisa (120 chiamate al minuto, di cui al massimo 30 possono mutare), così aprire tre dispositivi non triplica la quota di nessuno e un membro rumoroso non può affamare gli altri.
- L’annullamento è per principale. Il diario delle azioni di ogni client registra solo le mutazioni compiute dal suo principale. Puoi fare ⌘Z sulla tua modifica a un ticket; non puoi annullare quella di un collega: nel momento in cui la vedi, la corsia durevole l’ha già risolta (vedi sotto).
L’appartenenza è il confine di accesso, non la chiave di abbinamento
Sezione intitolata “L’appartenenza è il confine di accesso, non la chiave di abbinamento”Due credenziali distinte rispondono a due domande distinte. La chiave di abbinamento (vedi Controllo remoto e mobile) autentica un dispositivo presso il server. L’appartenenza autorizza l’utente di quel dispositivo ad agire all’interno di uno spazio di lavoro. Un dispositivo abbinato il cui utente non ha alcuna appartenenza a uno spazio di lavoro non può né leggerlo né modificarlo.
L’appartenenza è una riga per coppia (spazio di lavoro, utente), memorizzata nel file di database dello spazio di lavoro stesso — quindi «chi c’è in questo spazio di lavoro?» è una normale lettura con ambito, mentre la domanda inversa, «in quali spazi di lavoro mi trovo?», attraversa necessariamente più file ed è uno dei pochi fan-out tra spazi di lavoro consentiti. È ciò che esegue il selettore dello spazio di lavoro prima che ne sia stato scelto uno.
Ogni riga porta un ruolo da una scala a cinque livelli ordinata per rango:
| Ruolo | Può |
|---|---|
owner |
Controllo completo, inclusa l’eliminazione dello spazio di lavoro; esattamente uno per spazio di lavoro |
admin |
Gestire membri, inviti, impostazioni, operazioni distruttive |
member |
Lettura-scrittura quotidiana: messaggi, ticket, esecuzioni di agenti |
viewer |
Sola lettura su tutto lo spazio di lavoro; i glob di esclusione dei segreti nascondono i percorsi corrispondenti sulle superfici che espongono codice |
guest |
Stessa soglia di sola lettura dell’osservatore, al rango più basso della scala |
Ogni decisione di accesso si riduce a «il ruolo del chiamante è almeno X». La
soglia minima è derivata dal tipo di operazione, a meno che questa non ne
dichiari una esplicitamente: una lettura richiede guest, una mutazione
richiede member, un’operazione distruttiva richiede admin. Le ACL per
oggetto oltre alle concessioni per repository descritte sotto sono
deliberatamente fuori ambito.
Gli inviti sono il modo in cui si concede l’appartenenza, da Impostazioni →
Spazio di lavoro → Membri. Un admin conia un codice monouso (come link o QR)
che scade dopo sette giorni per impostazione predefinita; il server memorizza
solo l’hash SHA-256 del codice e il codice stesso viene mostrato una sola
volta. Il suo riscatto esegue il provisioning JIT dell’utente, registra
l’appartenenza con il ruolo dell’invito (mai owner — la proprietà si
trasferisce esplicitamente, non si concede mai tramite invito) e passa
all’abbinamento del dispositivo. Le credenziali del dispositivo generate dopo
l’abbinamento hanno a loro volta una scadenza di 30 giorni, quindi un
abbinamento non è una chiave permanente.
Le concessioni per repository stanno sotto l’appartenenza. Poiché il
server detiene checkout completi, la sola appartenenza a uno spazio di lavoro
non deve mai attribuire più privilegi della forge: ogni superficie che espone
codice — file, diff, revisione delle PR, ricerca nel codice — verifica la
concessione del membro su quel repository. Le concessioni sono per (spazio
di lavoro, utente, repository) e ordinate none → read → review →
write, dove write significa guidare esecuzioni di agenti che scrivono nel
repository. L’assenza di una riga significa none; proprietari e admin
detengono implicitamente write su ogni repository collegato e non hanno
alcuna riga.
Due corsie per lo stato di collaborazione
Sezione intitolata “Due corsie per lo stato di collaborazione”Control Center divide lo stato condiviso in due corsie con regole di durabilità opposte, perché i due tipi di stato falliscono in modi opposti.
La corsia effimera della presenza
Sezione intitolata “La corsia effimera della presenza”La presenza — chi è online, dove si trova, cosa sta digitando, cosa sta facendo
un agente in questo momento — è inutile nel momento stesso in cui diventa
obsoleta. Se fosse persistita, ogni riconnessione dovrebbe riconciliare «il
database dice che Sam sta modificando questo ticket» con la realtà. Perciò la
presenza non viene mai scritta nel database. Vive in un hub della
presenza in memoria sul server: i client pubblicano con presence.update, i
peer si iscrivono con presence.watch e le voci scadono automaticamente dopo
tre heartbeat mancati (gli heartbeat vengono inviati ogni 10 secondi; l’hub
esegue una scansione ogni 5). Non c’è nulla da riconciliare: solo da lasciar
decadere.
Due proprietà mantengono affidabile la corsia:
- L’identità proviene dalla sessione, mai dagli argomenti del client. Il
server mappa il dispositivo autenticato della connessione su un id utente; un
client può sempre e solo aggiornare la propria presenza, quindi nessuno può
falsificare lo stato o la posizione di un altro partecipante. Un aggiornamento
offlineesplicito (disconnessione o l’interruttore dell’invisibilità) rimuove la voce immediatamente. - L’elenco è accentrato sul server. I peer non si scambiano mai la presenza direttamente, quindi ogni partecipante vede lo stesso elenco indipendentemente dalla topologia. Gli agenti non hanno un client, quindi il server sintetizza le loro voci dallo stato di esecuzione in tempo reale — sta pensando, in corso, bloccato su un gate di approvazione, completato — con un’unica esecuzione rappresentativa che determina stato e posizione di un agente, così le esecuzioni concorrenti non fanno sfarfallare l’elenco.
Il fan-out è suddiviso in livelli per consumatore: desktop e web viaggiano su un livello completo (al massimo un’emissione dell’elenco ogni 100 ms), il telefono su un livello riassuntivo (ogni 500 ms), così gli aggiornamenti a cadenza di cursore non possono fondere la PWA.
La posizione di un partecipante — dove si trova in questo momento, fino
all’artefatto — è un’unione sealed: Space, File (repository + percorso +
riga facoltativa), Pr, Ticket, PlanNode. Non ogni posizione ha una
destinazione a cui saltare: non esiste una route di visualizzazione dei file,
quindi una posizione File viene resa come etichetta anziché come
destinazione. La corsia trasporta anche rivendicazioni non bloccanti («Sam
sta modificando questo ticket», «un agente detiene questo worktree»):
visibilità dei conflitti, non blocchi. I tipi di posizione sconosciuti
vengono interpretati come null, quindi un client vecchio semplicemente non
rende ciò che non capisce.
La corsia condivisa durevole
Sezione intitolata “La corsia condivisa durevole”I record che devono sopravvivere — ticket, il documento Note condiviso dello spazio, messaggi — seguono il percorso opposto: autorevoli, ordinati e persistiti. Le modifiche concorrenti si risolvono con last-writer-wins nell’ordine di ricezione del server, mai in base agli orologi dei client. La risoluzione è deliberatamente a grana fine: le modifiche ai ticket sono per colonna, quindi due persone che modificano campi diversi dello stesso ticket hanno entrambe successo e nessuna sovrascrive l’altra (le transizioni del flusso di lavoro mantengono invece il locking ottimistico); il documento Note dello spazio è una sola colonna, quindi l’intero documento è LWW e le rivendicazioni non bloccanti sulla corsia della presenza rendono visibile la modifica concorrente invece di bloccarla. Non è deliberatamente un CRDT: l’onere della prova spetta al CRDT, e il LWW nell’ordine di ricezione più le rivendicazioni visibili copre i modelli di collaborazione reali.
«Ultimo scrittore» significa ordine di ricezione del server, mai l’orologio di un client: un portatile con un orologio sfasato non può vincere una modifica che ha perso. Dietro la corsia durevole c’è il feed di sincronizzazione: i trigger SQLite accodano ogni mutazione a un log delle modifiche per spazio di lavoro all’interno della transazione di scrittura, assegnando un numero di sequenza monotono per spazio di lavoro nella stessa transazione della mutazione stessa. Un servizio del server segue quel log e lo trasforma in frame delta versionati in ordine di sequenza. I client evolvono da snapshot completi a delta; una lacuna nella sequenza riporta quello store in modalità snapshot invece di applicare frame in modo errato.
Segui, guida, prendi il controllo e restituiscilo
Sezione intitolata “Segui, guida, prendi il controllo e restituiscilo”La corsia della presenza non è solo un elenco: guida i verbi della collaborazione:
- Modalità Segui. Fai clic su un partecipante e il tuo client si aggancia alla sua posizione, navigando mentre lui naviga. Segui vale «finché non agisci»: nel momento in cui navighi di tua iniziativa, l’inseguimento si stacca. Un banner chiudibile segnala quando un altro partecipante sta presentando uno spazio in cui ti trovi.
- Guida. Un messaggio a un agente in esecuzione viene messo in coda come guida; l’agente lo rilegge prima di riprendere il turno successivo, così puoi indirizzare un’esecuzione in corsa senza interromperla.
- Prendi il controllo. Assumere il controllo di una conversazione mette in pausa ogni esecuzione attiva al suo prossimo confine di turno pulito (un’esecuzione di Claude Code, che non ha un confine sicuro, viene arrestata) dietro uno snapshot git per turno, così la presa di controllo è reversibile. Un marcatore durevole blocca i nuovi invii nello spazio e sopravvive al riavvio del server: il server non riprende mai automaticamente dentro la modifica a metà di una persona. Un solo editor attivo per worktree è una rivendicazione non bloccante: un secondo tentativo di presa di controllo è visibile e rifiutato, non fuso in silenzio.
- Restituisci il controllo. Restituire il controllo pubblica nello spazio un riepilogo strutturato del diff del worktree, lo accoda come guida per le esecuzioni in pausa, le riprende e cancella il marcatore.
Il quadrante dell’autonomia
Sezione intitolata “Il quadrante dell’autonomia”La guida e la presa di controllo sono interventi successivi ai fatti. Il quadrante dell’autonomia è l’impostazione permanente: per spazio, per agente, impostata dall’intestazione dello spazio, decide cosa succede quando un agente raggiunge un’azione controllata da un gate.
| Livello | Valore di serializzazione | Effetto su una chiamata a uno strumento controllata da gate |
|---|---|---|
| Solo proposta | proposeOnly |
Rifiutata del tutto, con una motivazione che l’agente deve riferire: può descrivere l’azione ma non compierla |
| Agisci con approvazione | actWithApproval |
Il gate di approvazione fail-closed: una conferma compare a un operatore connesso e l’assenza di un approvatore significa rifiuto |
| Agisci liberamente | actFreely |
Pre-approvata, tranne dove si applica una regola deny rigida dei guardrail: una regola di rifiuto vince sempre |
| (non impostato) | null |
Come agisci con approvazione: il valore predefinito del server |
Due cose restano fuori dal quadrante in entrambe le direzioni. Il verbo di
output di una modalità è fissato e non può mai essere negato, qualunque cosa
dica il quadrante: la modalità orchestrate una volta era strutturalmente
incapace di emettere il suo unico deliverable perché un preset negava la classe
di effetto dichiarata da quel verbo. E un deny rigido dai guardrail delle
azioni non è aggirabile con actFreely.
Zero regressioni per il lavoro in solitaria
Sezione intitolata “Zero regressioni per il lavoro in solitaria”Tutto questo è additivo. Il flusso di lavoro di chi opera da solo è invariato: con un solo essere umano e nessun agente, l’hub della presenza non ha nulla da trasmettere, quindi la corsia emette il suo snapshot iniziale e poi resta inattiva a traffico zero; il meccanismo LWW e di sincronizzazione è lo stesso percorso a scrittore singolo di sempre; e la chiave di abbinamento resta la credenziale del dispositivo che è sempre stata. L’appartenenza è già il confine di accesso: con un solo essere umano c’è un’unica riga di proprietario e nessun elemento di interfaccia dell’elenco.
Concetti correlati
Sezione intitolata “Concetti correlati”- Spazi di lavoro e isolamento: il confine che l’appartenenza applica
- Controllo remoto e mobile: il lato identità fatto di chiave di abbinamento e credenziale del dispositivo
- Il modello degli agenti: la metà agente dell’unione Principal
- Ciclo di vita dell’invio degli agenti: le esecuzioni che guida e presa di controllo mettono in pausa e riprendono
- Guardrail: la policy per classe di effetto con cui si compone il quadrante dell’autonomia
- Eventi di dominio: il bus su cui viaggiano gli aggiornamenti in tempo reale