Multiplayer — identiteit, lidmaatschap en aanwezigheid
Control Center werkt voor één enkele operator, maar het model eronder is vanaf het begin multi-actor. Mensen en agenten berichten in dezelfde ruimtes, bewerken dezelfde tickets en staan op dezelfde deelnemerslijst. Dat veilig en begrijpelijk maken komt neer op drie beslissingen: wie als actor telt, wat de toegang begrenst en welke toestand duurzaam wordt gedeeld en welke alleen live wordt uitgezonden.
Principalen: gebruikers en agenten zijn gelijkwaardige actoren
Sectie getiteld “Principalen: gebruikers en agenten zijn gelijkwaardige actoren”Alles wat vastlegt “wie dit heeft gedaan” — berichten, tickets, reviews,
plannen, runlogboeken, auditregels — loopt via een Principal, een
verzegelde union van UserPrincipal en AgentPrincipal met een compacte
draadvorm (user:<id> / agent:<id>). Toeschrijving vouwt nooit N mensen (of
N agenten) samen in één sentinelaccount, dus een review die een agent
achterliet en een review die een persoon achterliet zijn overal waar ze
opduiken te onderscheiden.
Omdat de principal de eenheid van identiteit is, is het ook de eenheid van inkapseling:
- Snelheidslimieten gelden per principal, niet per sessie. De overbelastingsbeveiliging op externe toolaanroepen budgetteert per gebruiker, niet per verbinding: alle apparaten van een lid putten uit één gedeeld schuivend venster (120 aanroepen per minuut, waarvan er hoogstens 30 mogen muteren), dus drie apparaten openzetten verdrievoudigt niemands tegoed en één luidruchtig lid kan de anderen niet uithongeren.
- Ongedaan maken geldt per principal. Het actiejournaal van elke client registreert alleen de mutaties die zijn principal heeft uitgevoerd. Je kunt je eigen ticketbewerking met ⌘Z terugdraaien; die van een teamgenoot niet — tegen de tijd dat je hem ziet, heeft het duurzame spoor hem al opgelost (zie hieronder).
Lidmaatschap is de toegangsgrens — niet de koppelingssleutel
Sectie getiteld “Lidmaatschap is de toegangsgrens — niet de koppelingssleutel”Twee afzonderlijke inloggegevens beantwoorden twee afzonderlijke vragen. De koppelingssleutel (zie Afstandsbediening en mobiel) authenticeert een apparaat bij de server. Lidmaatschap machtigt de gebruiker van dat apparaat om binnen een werkruimte te handelen. Een gekoppeld apparaat waarvan de gebruiker geen lidmaatschap in een werkruimte heeft, kan die werkruimte niet lezen en er niets in wijzigen.
Lidmaatschap is één rij per (werkruimte, gebruiker), opgeslagen in het eigen databasebestand van die werkruimte — dus “wie zit er in deze werkruimte?” is een gewone leesopdracht binnen één scope, terwijl de omgekeerde vraag, “in welke werkruimtes zit ik?”, noodzakelijk bestanden overspant en een van de weinige toegestane fan-outs over werkruimtes heen is. Het is wat de werkruimtekiezer uitvoert voordat er een werkruimte is gekozen.
Elke rij draagt een rol uit een vijfledige, op rang geordende ladder:
| Rol | Mag |
|---|---|
owner |
Volledige controle, inclusief het verwijderen van de werkruimte; precies één per werkruimte |
admin |
Leden, uitnodigingen, instellingen en destructieve bewerkingen beheren |
member |
Dagelijks lezen en schrijven: berichten, tickets, agentruns |
viewer |
Alleen-lezen in de hele werkruimte; globs voor het uitsluiten van secrets verbergen overeenkomende paden op oppervlakken met code |
guest |
Dezelfde alleen-lezen-ondergrens als viewer, op de laagste rang van de ladder |
Elke toegangsbeslissing komt neer op “is de rol van de aanvrager minstens X”.
De ondergrens wordt afgeleid uit de soort bewerking, tenzij die er expliciet
een aangeeft: een lezing vereist guest, een mutatie vereist member, een
destructieve bewerking vereist admin. ACL’s per object, naast de
repo-toekenningen hieronder, vallen bewust buiten scope.
Uitnodigingen zijn hoe lidmaatschap wordt verleend, vanuit Instellingen →
Werkruimte → Leden. Een admin maakt een eenmalige code aan (als link of QR)
die standaard na zeven dagen verloopt; de server bewaart alleen de
SHA-256-hash van de code en de code zelf wordt precies één keer getoond. Het
inwisselen voorziet de gebruiker JIT van een account, legt het lidmaatschap
vast in de rol van de uitnodiging (nooit owner — eigenaarschap wordt
expliciet overgedragen, nooit per uitnodiging verleend) en draagt over aan de
apparaatkoppeling. Apparaatreferenties die na het koppelen worden uitgegeven,
zijn zelf beperkt tot 30 dagen, dus een koppeling is geen permanente sleutel.
Repo-toekenningen liggen onder het lidmaatschap. Omdat de server volledige
checkouts houdt, mag lidmaatschap van de werkruimte alleen nooit meer rechten
geven dan de forge: elk oppervlak met code — bestanden, diffs, PR-review,
codezoekopdrachten — controleert de toekenning van het lid op die repo.
Toekenningen gelden per (werkruimte, gebruiker, repo) en zijn gerangschikt
none → read → review → write, waarbij write betekent: agentruns
aansturen die naar de repo schrijven. Het ontbreken van een rij betekent
none; owners en admins hebben impliciet write op elke gekoppelde repo en
dragen helemaal geen rijen.
Twee sporen van gedeelde samenwerkingsstaat
Sectie getiteld “Twee sporen van gedeelde samenwerkingsstaat”Control Center splitst gedeelde toestand in twee sporen met tegengestelde duurzaamheidsregels, omdat de twee soorten toestand op tegengestelde manieren falen.
Het vluchtige aanwezigheidsspoor
Sectie getiteld “Het vluchtige aanwezigheidsspoor”Aanwezigheid — wie online is, waar ze zijn, wat ze typen, wat een agent op dit
moment doet — is waardeloos zodra ze verouderd is. Als ze werd gepersisteerd,
zou elke herverbinding de bewering “de database zegt dat Sam dit ticket
bewerkt” tegen de werkelijkheid moeten afwegen. Daarom wordt aanwezigheid
nooit naar de database geschreven. Ze leeft in een in-memory
aanwezigheidshub op de server: clients publiceren met presence.update,
peers abonneren zich met presence.watch en vermeldingen verlopen automatisch
na drie gemiste heartbeats (heartbeats lopen elke 10 seconden; de hub ruimt
elke 5 op). Er valt niets af te stemmen — alleen iets te laten verlopen.
Twee eigenschappen houden het spoor betrouwbaar:
- Identiteit komt uit de sessie, nooit uit clientargumenten. De server
koppelt het geauthenticeerde apparaat van de verbinding aan een
gebruikers-id; een client kan alleen zijn eigen aanwezigheid bijwerken, dus
niemand kan de status of locus van een andere deelnemer vervalsen. Een
expliciete
offline-update (verbinding verbreken of de onzichtbaarheidsschakelaar) verwijdert de vermelding onmiddellijk. - De deelnemerslijst loopt via de server. Peers wisselen aanwezigheid nooit rechtstreeks uit, dus elke deelnemer ziet dezelfde lijst, ongeacht de topologie. Agenten hebben geen client, dus de server synthetiseert hun vermeldingen uit de live runstatus — aan het denken, aan het draaien, geblokkeerd op een goedkeuringspoort, klaar — waarbij één representatieve run de status en locus van een agent bepaalt, zodat gelijktijdige runs de lijst niet laten flapperen.
De fan-out is gelaagd per consument: desktop en web rijden op een volledige laag (hoogstens één lijstuitzending per 100 ms), de telefoon op een samenvattingslaag (per 500 ms), zodat updates op cursustempo de PWA niet laten smelten.
De locus van een deelnemer — waar die zich op dit moment bevindt, tot op
het artefact — is een verzegelde union: Space, File (repo + pad +
optionele regel), Pr, Ticket, PlanNode. Niet elke locus heeft een plek
om naartoe te springen: er is geen bestandsviewerroute, dus een File-locus
wordt weergegeven als een label in plaats van als bestemming. Het spoor draagt
ook zachte claims (“Sam bewerkt dit ticket”, “een agent houdt deze worktree
vast”): zichtbaarheid van conflicten, geen locks. Onbekende locussoorten parsen
naar null, dus een oude client geeft eenvoudigweg niet weer wat hij niet
begrijpt.
Het duurzame gedeelde spoor
Sectie getiteld “Het duurzame gedeelde spoor”Records die moeten overleven — tickets, het gedeelde Notes-document van de ruimte, berichten — nemen het tegenovergestelde pad: gezaghebbend, geordend en gepersisteerd. Gelijktijdige bewerkingen worden opgelost met last-writer-wins in de ontvangstvolgorde van de server, nooit met clientklokken. De oplossing is bewust fijnmazig: ticketbewerkingen zijn per kolom, dus twee mensen die verschillende velden van hetzelfde ticket bewerken, landen allebei en geen van beiden overschrijft de ander (workflowovergangen behouden juist optimistische vergrendeling); het Notes-document van de ruimte is één kolom, dus het hele document is LWW, waarbij zachte claims op het aanwezigheidsspoor gelijktijdig bewerken zichtbaar maken in plaats van vergrendeld. Het is bewust geen CRDT — de bewijslast ligt bij de CRDT, en LWW op ontvangstvolgorde plus zichtbare claims dekt de werkelijke samenwerkingspatronen.
“Last writer” betekent ontvangstvolgorde op de server, nooit een clientklok — een laptop met een scheve klok kan een bewerking die hij verloor niet winnen. Achter het duurzame spoor zit de syncfeed: SQLite-triggers voegen elke mutatie toe aan een wijzigingenlogboek per werkruimte binnen de schrijvende transactie, en kennen een monotoon volgnummer per werkruimte toe in dezelfde transactie als de mutatie zelf. Een serverdienst volgt dat log in versiegebonden deltaframes in volgorde. Clients evolueren van volledige snapshots naar delta’s; een gat in de reeks laat die opslag terugvallen op snapshotmodus in plaats van frames verkeerd toe te passen.
Volgen, bijsturen, overnemen, teruggeven
Sectie getiteld “Volgen, bijsturen, overnemen, teruggeven”Het aanwezigheidsspoor is niet alleen een deelnemerslijst — het stuurt de samenwerkingswerkwoorden aan:
- Volgmodus. Klik op een deelnemer en je client rijdt mee op diens locus en navigeert terwijl die navigeert. Volgen geldt “totdat je zelf iets doet”: op het moment dat je op eigen kracht navigeert, wordt het volgen losgekoppeld. Een wegklikbare banner meldt wanneer een andere deelnemer een ruimte waarin jij zit in de spotlight zet (presenteert).
- Bijsturen. Een bericht aan een draaiende agent wordt in de wachtrij gezet als sturing; de agent leest het opnieuw voordat hij aan zijn volgende beurt begint, zodat je een run midden in de vlucht kunt bijsturen zonder hem te doden.
- Overnemen. De controle over een gesprek overnemen pauzeert elke actieve run op zijn volgende schone beurtgrens (een Claude Code-run, die geen veilige grens heeft, wordt gestopt) achter een git-snapshot per beurt, zodat het overnemen omkeerbaar is. Een duurzame markering blokkeert nieuwe dispatches naar de ruimte en overleeft een herstart van de server — de server hervat nooit automatisch in de half afgemaakte bewerking van een mens. Eén live bewerker per worktree is een zachte claim: een tweede overname is zichtbaar en wordt geweigerd, niet stil samengevoegd.
- Teruggeven. De controle teruggeven plaatst een gestructureerde diffsamenvatting van de worktree in de ruimte, zet die als sturing in de wachtrij voor de gepauzeerde runs, hervat ze en wist de markering.
De autonomieregelaar
Sectie getiteld “De autonomieregelaar”Bijsturen en overnemen zijn interventies achteraf. De autonomieregelaar is de vaste instelling: per ruimte, per agent, ingesteld vanuit de ruimteheader, bepaalt hij wat er gebeurt wanneer een agent een afgeschermde actie bereikt.
| Niveau | Draadwaarde | Effect op een afgeschermde toolaanroep |
|---|---|---|
| Alleen voorstellen | proposeOnly |
Botweg geweigerd, met een reden die de agent moet doorgeven — hij kan de actie beschrijven maar niet uitvoeren |
| Handelen met goedkeuring | actWithApproval |
De fail-closed goedkeuringspoort: één bevestiging verschijnt bij een verbonden operator en zonder goedkeurder betekent het weigering |
| Vrij handelen | actFreely |
Vooraf goedgekeurd, behalve waar een harde deny-regel uit de beveiligingen van toepassing is — een deny-regel wint altijd |
| (niet ingesteld) | null |
Hetzelfde als handelen met goedkeuring: de standaardwaarde van de server |
Twee dingen vallen in beide richtingen buiten de regelaar. Het eigen
uitvoerwerkwoord van een modus is vastgezet en kan nooit worden geweigerd, wat
de regelaar ook zegt — de orchestrate-modus kon ooit structureel zijn enige
deliverable niet uitstoten omdat een preset de effectklasse weigerde die dat
werkwoord declareert. En een harde deny uit de actiebeveiligingen kan niet
door actFreely worden overschreven.
Geen regressie in solo gebruik
Sectie getiteld “Geen regressie in solo gebruik”Dit alles is additief. De workflow van een solo-operator verandert niet: met één mens en geen agenten heeft de aanwezigheidshub niets uit te zenden, dus het spoor stuurt zijn beginsnapshot en blijft daarna inactief met nul verkeer; de LWW- en sync-machinerie is hetzelfde enkelvoudige-schrijverpad als altijd; en de koppelingssleutel blijft de apparaatreferentie die hij altijd al was. Lidmaatschap is al de toegangsgrens — met één mens is er één owner-rij en geen deelnemerslijst-opsmuk.
Gerelateerde concepten
Sectie getiteld “Gerelateerde concepten”- Werkruimtes en isolatie: de grens die lidmaatschap handhaaft
- Afstandsbediening en mobiel: de koppelingssleutel en de apparaatreferentie-kant van identiteit
- Het agentmodel: de agenthelft van de principal-union
- Levenscyclus van agent-dispatch: de runs die bijsturen en overnemen pauzeren en hervatten
- Beveiligingen: het effectklassebeleid waarmee de autonomieregelaar samengaat
- Domeingebeurtenissen: de bus waarop live-updates meerijden