Multijogador — identidade, pertença e presença
O Control Center funciona para um único operador, mas o modelo subjacente é multiator desde o início. Humanos e agentes trocam mensagens nos mesmos espaços, editam os mesmos tickets e aparecem na mesma lista de participantes. Tornar isto seguro e compreensível resume-se a três decisões: quem conta como ator, o que condiciona o acesso e que estado é partilhado de forma durável e qual é apenas difundido em direto.
Principals: utilizadores e agentes são atores em pé de igualdade
Secção intitulada “Principals: utilizadores e agentes são atores em pé de igualdade”Tudo o que regista “quem fez isto” — mensagens, tickets, revisões, planos,
registos de execução, entradas de auditoria — resolve-se através de um
Principal, uma união selada de UserPrincipal e AgentPrincipal com uma
forma compacta no protocolo (user:<id> / agent:<id>). A atribuição nunca
colapsa N humanos (ou N agentes) numa única conta sentinela, pelo que uma
revisão deixada por um agente e uma revisão deixada por uma pessoa são
distinguíveis onde quer que apareçam.
Como o principal é a unidade de identidade, é também a unidade de contenção:
- Os limites de taxa são por principal, não por sessão. A proteção contra inundação nas chamadas remotas de ferramentas é orçamentada por utilizador, não por ligação: todos os dispositivos de um membro consomem a mesma janela deslizante partilhada (120 chamadas por minuto, das quais no máximo 30 podem alterar estado), pelo que abrir três dispositivos não triplica a quota de ninguém e um membro ruidoso não deixa os restantes sem quota.
- Anular é por principal. O diário de ações de cada cliente regista apenas as mutações que o seu próprio principal realizou. Pode anular com ⌘Z a sua própria edição de ticket; não pode anular a de um colega — quando a vê, o canal durável já a resolveu (ver abaixo).
A pertença é a fronteira de acesso — não a chave de emparelhamento
Secção intitulada “A pertença é a fronteira de acesso — não a chave de emparelhamento”Duas credenciais separadas respondem a duas perguntas separadas. A chave de emparelhamento (ver Controlo remoto e telemóvel) autentica um dispositivo no servidor. A pertença autoriza o utilizador desse dispositivo a agir dentro de um espaço de trabalho. Um dispositivo emparelhado cujo utilizador não tem pertença num espaço de trabalho não o consegue ler nem alterar.
A pertença é uma linha por (espaço de trabalho, utilizador), guardada no ficheiro de base de dados do próprio espaço de trabalho — pelo que “quem está neste espaço de trabalho?” é uma leitura corrente limitada ao âmbito, enquanto a pergunta inversa, “em que espaços de trabalho estou?”, atravessa necessariamente ficheiros e é um dos poucos fan-outs sancionados entre espaços de trabalho. É o que o seletor de espaços de trabalho executa antes de qualquer espaço ter sido escolhido.
Cada linha transporta um papel de uma hierarquia de cinco níveis, ordenada por precedência:
| Papel | Pode |
|---|---|
owner |
Controlo total, incluindo a eliminação do espaço de trabalho; exatamente um por espaço de trabalho |
admin |
Gerir membros, convites, definições e operações destrutivas |
member |
Leitura e escrita do dia a dia: mensagens, tickets, execuções de agente |
viewer |
Só de leitura em todo o espaço de trabalho; os globs de exclusão de segredos ocultam os caminhos correspondentes nas superfícies que contêm código |
guest |
O mesmo nível mínimo de só de leitura do observador, no posto mais baixo da hierarquia |
Todas as decisões de acesso resumem-se a “o papel de quem chama é, no mínimo,
X”. O nível mínimo é derivado do tipo de operação, a menos que esta declare um
explicitamente: uma leitura exige guest, uma mutação exige member, uma
operação destrutiva exige admin. As ACLs por objeto, para além das concessões
por repositório abaixo descritas, ficam deliberadamente fora do âmbito.
Os convites são a forma de conceder pertença, em Definições → Espaço de
trabalho → Membros. Um admin emite um código de uso único (como ligação ou QR)
que expira ao fim de sete dias por predefinição; o servidor guarda apenas o
hash SHA-256 do código, e o próprio código é mostrado exatamente uma vez. Ao
resgatá-lo, o utilizador é aprovisionado de imediato, a pertença é registada
com o papel do convite (nunca owner — a propriedade é transferida
explicitamente, nunca concedida por convite) e passa-se ao emparelhamento do
dispositivo. As credenciais de dispositivo emitidas após o emparelhamento têm
elas próprias um prazo de 30 dias, pelo que um emparelhamento não é uma chave
permanente.
As concessões por repo ficam abaixo da pertença. Como o servidor mantém
checkouts completos, a pertença a um espaço de trabalho, por si só, nunca pode
conferir mais privilégios do que o host de código: todas as superfícies que
contêm código — ficheiros, diffs, revisão de PR, pesquisa de código —
verificam a concessão do membro nesse repo. As concessões são por (espaço de
trabalho, utilizador, repo) e classificadas como none → read → review →
write, em que write significa conduzir execuções de agente que escrevem no
repo. A ausência de uma linha significa none; os proprietários e os admins
detêm implicitamente write em todos os repos ligados e não têm linha alguma.
Dois canais de estado de colaboração
Secção intitulada “Dois canais de estado de colaboração”O Control Center divide o estado partilhado em dois canais com regras de durabilidade opostas, porque os dois tipos de estado falham de formas opostas.
O canal efémero de presença
Secção intitulada “O canal efémero de presença”A presença — quem está online, onde está, o que está a escrever, o que um
agente está a fazer neste instante — não vale nada no momento em que fica
desatualizada. Se fosse persistida, todas as reconexões teriam de reconciliar
“a base de dados diz que o Sam está a editar este ticket” com a realidade. Por
isso, a presença nunca é escrita na base de dados. Vive num hub de
presença em memória no servidor: os clientes publicam com presence.update,
os pares subscrevem com presence.watch e as entradas expiram automaticamente
ao fim de três sinais de atividade perdidos (os sinais de atividade são
enviados a cada 10 segundos; o hub faz uma varredura a cada 5). Não há nada
para reconciliar — apenas para deixar caducar.
Duas propriedades mantêm o canal fiável:
- A identidade vem da sessão, nunca dos argumentos do cliente. O servidor
mapeia o dispositivo autenticado da ligação para um id de utilizador; um
cliente só pode atualizar a sua própria presença, pelo que ninguém consegue
falsificar o estado ou o locus de outro participante. Uma atualização
offlineexplícita (desligação da ligação ou o comutador de invisibilidade) remove a entrada de imediato. - A lista de participantes é centralizada no servidor. Os pares nunca trocam presença diretamente, pelo que todos os participantes veem a mesma lista, independentemente da topologia. Os agentes não têm cliente, pelo que o servidor sintetiza as suas entradas a partir do estado das execuções em direto — a pensar, a executar, bloqueado num portão de aprovação, concluído — com uma única execução representativa a decidir o estado e o locus de um agente, para que execuções simultâneas não façam a lista oscilar.
O fan-out é escalonado por consumidor: o computador e a web usam um nível completo (no máximo uma emissão da lista a cada 100 ms), o telemóvel um nível de resumo (a cada 500 ms), para que as atualizações à cadência do cursor não derretam a PWA.
O locus de um participante — onde está neste momento, até ao nível do
artefacto — é uma união selada: Space, File (repo + caminho + linha
opcional), Pr, Ticket, PlanNode. Nem todos os locus têm um destino para
onde saltar: não existe uma rota de visualização de ficheiros, pelo que um
locus File é apresentado como etiqueta em vez de destino. O canal também
transporta reivindicações leves (“o Sam está a editar este ticket”, “um
agente detém este worktree”): visibilidade de conflitos, não bloqueios. Os
tipos de locus desconhecidos são interpretados como null, pelo que um cliente
antigo simplesmente não apresenta o que não compreende.
O canal durável partilhado
Secção intitulada “O canal durável partilhado”Os registos que têm de sobreviver — tickets, o documento de Notas partilhado do espaço, mensagens — seguem o caminho oposto: autoritativos, ordenados e persistidos. As edições simultâneas resolvem-se por última escrita vence (LWW) pela ordem de receção no servidor, nunca por relógios de cliente. A resolução é deliberadamente granular: as edições de tickets são por coluna, pelo que duas pessoas a editar campos diferentes do mesmo ticket são ambas aplicadas e nenhuma sobrepõe a outra (as transições de fluxo de trabalho mantêm, em vez disso, o bloqueio otimista); o documento de Notas do espaço é uma única coluna, pelo que o documento inteiro é LWW, com as reivindicações leves no canal de presença a tornar a edição simultânea visível em vez de bloqueada. Deliberadamente, não é um CRDT — o ónus da prova recai sobre o CRDT, e o LWW por ordem de receção mais reivindicações visíveis cobre os padrões reais de colaboração.
“Último a escrever” significa ordem de receção no servidor, nunca um relógio de cliente — um portátil com o relógio desalinhado não consegue ganhar uma edição que perdeu. Por trás do canal durável está o feed de sincronização: os disparadores do SQLite acrescentam cada mutação a um registo de alterações por espaço de trabalho dentro da transação de escrita, atribuindo um número de sequência monotónico por espaço de trabalho na mesma transação da própria mutação. Um serviço do servidor acompanha esse registo e transforma-o em frames delta versionados, pela ordem da sequência. Os clientes evoluem de instantâneos completos para deltas; uma lacuna na sequência faz esse armazenamento recuar para o modo de instantâneo em vez de aplicar frames incorretamente.
Seguir, orientar, assumir o controlo, devolver o controlo
Secção intitulada “Seguir, orientar, assumir o controlo, devolver o controlo”O canal de presença não é apenas uma lista — comanda os verbos de colaboração:
- Modo de seguimento. Clique num participante e o seu cliente acompanha o locus dele, navegando à medida que ele navega. O seguimento dura “até agir”: no momento em que navega pelos seus próprios meios, o seguimento desliga-se. É apresentado um banner dispensável quando outro participante está a destacar (apresentar) um espaço em que está.
- Orientar. Uma mensagem para um agente em execução é colocada em fila como orientação; o agente volta a lê-la antes de retomar o turno seguinte, pelo que pode dar um empurrão a uma execução a meio sem a matar.
- Assumir o controlo. Assumir o controlo de uma conversa pausa todas as execuções ativas no limite do turno seguinte em estado limpo (uma execução do Claude Code, que não tem um limite seguro, é interrompida) com um instantâneo do git por turno, pelo que a assunção é reversível. Um marcador durável bloqueia novos despachos para o espaço e sobrevive a um reinício do servidor — o servidor nunca retoma automaticamente para uma edição inacabada de uma pessoa. Um editor em direto por worktree é uma reivindicação leve: uma segunda assunção do controlo é visível e recusada, não silenciosamente fundida.
- Devolver o controlo. Devolver o controlo publica no espaço um resumo estruturado do diff do worktree, coloca-o em fila como orientação para as execuções em pausa, retoma-as e limpa o marcador.
O dial de autonomia
Secção intitulada “O dial de autonomia”A orientação e a assunção do controlo são intervenções feitas a posteriori. O dial de autonomia é a definição permanente: por espaço, por agente, definida no cabeçalho do espaço, decide o que acontece quando um agente chega a uma ação sujeita a portão.
| Nível | Valor no protocolo | Efeito numa chamada de ferramenta sujeita a portão |
|---|---|---|
| Somente propor | proposeOnly |
Recusada de imediato, com um motivo que o agente deve transmitir — pode descrever a ação, mas não executá-la |
| Agir com aprovação | actWithApproval |
O portão de aprovação que nega por omissão: é apresentada uma confirmação a um operador ligado e, sem aprovador, a ação é recusada |
| Agir livremente | actFreely |
Pré-aprovada, exceto quando se aplica uma regra deny rígida das barreiras — uma regra de negação vence sempre |
| (não definido) | null |
O mesmo que agir com aprovação: a predefinição do servidor |
Há duas coisas que ficam fora do dial, em ambas as direções. O verbo de saída
de um modo é fixo e nunca pode ser negado, diga o que disser o dial — o modo
orchestrate chegou a ser estruturalmente incapaz de emitir o seu único
entregável porque uma predefinição negava a classe de efeitos que esse verbo
declara. E um deny rígido proveniente das barreiras de ação não pode ser
sobreposto por actFreely.
Individual, sem regressões
Secção intitulada “Individual, sem regressões”Tudo isto é aditivo. O fluxo de trabalho de um utilizador individual não muda: com uma única pessoa e nenhum agente, o hub de presença não tem nada para difundir, pelo que o canal emite o seu instantâneo inicial e depois fica inativo, com tráfego zero; a lógica de LWW e de sincronização é o mesmo caminho de escritor único de sempre; e a chave de emparelhamento continua a ser a credencial de dispositivo que sempre foi. A pertença já é a fronteira de acesso — com uma única pessoa existe uma só linha de proprietário e nenhum elemento de interface da lista.
Conceitos relacionados
Secção intitulada “Conceitos relacionados”- Espaços de trabalho e isolamento: a fronteira que a pertença aplica
- Controlo remoto e telemóvel: o lado da identidade relativo à chave de emparelhamento e à credencial de dispositivo
- O modelo de agentes: a metade agente da união de principals
- Ciclo de vida do despacho de agentes: as execuções que a orientação e a assunção do controlo pausam e retomam
- Barreiras: a política de classes de efeitos com que o dial de autonomia se compõe
- Eventos de domínio: o barramento por onde passam as atualizações em direto