agent-chat
Visão Geral
Sessões de agentes pares coordenam-se trocando arquivos markdown em pastas de canal compartilhadas — sem supervisor, sem broker de mensagens, sem RAM compartilhada entre elas. Cada canal é um "chat em grupo". O tópico inteiro é markdown puro: comittável pelo Git, legível por humanos, reproduzível. Uma sessão que falha não perde nada — os arquivos são o estado.
O CLI apenas com stdlib (chat.py mais o pacote irmão agent_chat/) funciona em Windows, WSL e Linux. wait dorme entre verificações do sistema de arquivos e não faz chamadas de modelo.
Princípio central: converse através de arquivos, não uns com os outros. Resumos como artefatos, não transcrições completas passadas de um lado para o outro.
Esta é uma camada de dados de coordenação local, não um servidor MCP ou executor de agente. Mensagens, tarefas, concessões, bloqueios de caminho, eventos de capacidade/status e estado derivado não fazem chamadas de conclusão, embedding, rerank, serviço de grafo ou relay/provider.
Quando usar
- Múltiplas sessões OMP ou outros agentes compatíveis trabalhando no mesmo problema como iguais.
- Uma sessão precisa que outra faça algo e depois espera pelo resultado.
- Você quer um registro auditável de uma negociação entre agentes.
- Você precisa de vários chats em grupo independentes (um por tópico/time) — crie um canal para cada.
Quando NÃO usar: um único agente com subagentes baratos é mais barato e simples — esse padrão troca tokens por paralelismo, tolerância a falhas e auditabilidade. Se o orçamento de tokens for apertado, use apenas o caminho assíncrono/de handoff (poste um resumo ao final da sessão; a próxima sessão o lê) — esse modo custa quase nada.
Referência rápida
Use agent-chat <cmd> após pipx install agent-chat-plugin, ou
uvx --from agent-chat-plugin agent-chat <cmd> para cada invocação avulsa.
uvx não instala um comando persistente. Com uma habilidade autônoma, mantenha
SKILL.md, chat.py e todo o diretório agent_chat/ juntos e execute
python "/path/to/skill/chat.py" <cmd>. Os exemplos abreviarão isso como chat.py.
O plugin do Claude Code usa python "${CLAUDE_PLUGIN_ROOT}/chat.py" <cmd>.
Outros hosts usam um caminho real de checkout, não uma variável de plugin assumida.
A precedência da raiz é --root > $AGENT_CHAT_ROOT > ~/agent-chat; coloque --root
antes do subcomando. Raízes padrão de máquinas diferentes não são sincronizadas.
A instalação via PyPI inclui apenas os módulos CLI/ runtime. Os hooks/*.py opcionais vêm do
checkout/plugin e precisam de fiação explícita do ciclo de vida do host. Seus pontos de entrada
Python são portáteis; seu registro e contrato de saída não são automaticamente uma
integração OMP. Avisos de sessão/prompt são texto; Stop emite JSON systemMessage compatível com Claude. Hooks espiam sem avançar cursores e sempre saem com 0;
CLAUDE_PLUGIN_ROOT não resolvido ou raízes de plugin relativas ao script pulam com um
diagnóstico de uma linha no stderr.
| Faça isso | Comando |
|---|---|
| Criar um chat em grupo | chat.py init review --members alice,bob --topic "..." |
| Listar todos os chats em grupo | chat.py channels |
| Postar uma mensagem | chat.py post review --from alice --to bob --title "Esquema v0.2" --body-file msg.md |
| Transmitir para o grupo | chat.py post review --from alice --to all --title "..." (ou omita --to) |
| Ler o que há de novo para mim | chat.py read review --as bob |
| Aguardar uma resposta (0 tokens) | chat.py wait review --as alice --timeout 900 |
| Espiar recentes, manter cursor | chat.py peek review -n 3 |
| Reivindicar um marcador de tarefa | chat.py claim work task-12.md --as bob |
| Bloquear caminhos do workspace | chat.py lock review src/main.py --as alice --lease-seconds 300 |
| Verificar conflitos de caminho | chat.py check review src/main.py |
| Liberar bloqueio de caminho | chat.py unlock review <lock-id-or-path> --as alice |
| Recuperar bloqueio obsoleto | chat.py recover review <lock-id-or-path> --as bob --reason "obsoleto" |
| Recuperar transação de caminho pendente | chat.py recover-pending review --as alice |
| Renderizar estado do canal | chat.py state review |
| Compactar estado do canal | chat.py compact review --as alice |
| Postar evento de capacidade/status | chat.py event post review --from alice --type capability --harness omp |
| Ler eventos de adaptador | chat.py event read review --type capability |
Eventos neutros de adaptador usam a versão 1 do esquema e são transportados como corpos JSON em
mensagens comuns do canal. Eventos de capacidade anunciam apenas os primitivos portáteis
messages, cursors, wait, tasks, dependencies, leases, path_locks,
e state_summary. Eventos de status usam ready, busy, idle,
blocked ou stopped, com detalhes opcionais. Campos, primitivos, versões, carimbos de data/hora, identidades e status desconhecidos falham com erros EVENT_* estáveis.
Este protocolo não reivindica MCP, ACP, wake bridge ou execução de agente.
Tarefas estruturadas usam a mesma raiz do canal e escrevem registros JSON autoritativos
sob <root>/<channel>/tasks/; registros de concessão ativos vivem sob
<root>/<channel>/claims/<task-id>.<owner>.json. Cada mutação bem-sucedida de tarefa ou concessão
também posta um evento de auditoria através do protocolo de mensagem existente:
chat.py task create <channel> <task-id> --from <agent> --title <title>
chat.py task list <channel>
chat.py task show <channel> <task-id>
chat.py task update <channel> <task-id> --as <agent> [fields...]
chat.py task claim <channel> <task-id> --as <agent> --lease-seconds 300
chat.py task renew <channel> <task-id> --as <agent> --lease-seconds 300
chat.py task done <channel> <task-id> --as <agent>
chat.py task block <channel> <task-id> --as <agent>
chat.py task release <channel> <task-id> --as <agent>
chat.py task recover <channel> <task-id> --as <agent> \
--reason "sessão obsoleta" --lease-seconds 300
chat.py task recover-pending <channel> --as <agent> \
[--resolve-publication rollback|published]
claim, renew e recover obsoleto aceitam --lease-seconds, --lease ou
--ttl; o valor deve ser uma duração positiva finita. claim avança uma tarefa
pronta para in_progress, atribui o proprietário e escreve sua expiração atomicamente com o
registro de reivindicação. Apenas o proprietário pode renovar, liberar ou concluir uma
reivindicação não expirada. task done limpa uma reivindicação ativa atomicamente; quando
nenhuma reivindicação existe, preserva a transição direta da Tarefa 3. task release preserva da mesma forma a
transição não arrendada da Tarefa 3. Uma reivindicação expirada nunca é roubada silenciosamente:
task recover obsoleto requer um novo proprietário e motivo não vazio e registra
previous_owner, previous_lease_expires_at e recovery_reason.
Após uma falha, task recover-pending <channel> --as <agent> finaliza explicitamente
a limpeza/rollback do marcador de transação durável; nunca rouba ou reatribui silenciosamente uma tarefa obsoleta. Marcadores applied legados v1 cuja
publicação de auditoria não pode ser comprovada falham fechados com LEASE_TRANSACTION_PUBLICATION_UNKNOWN;
reexecute o comando com --resolve-publication rollback ou published após
inspeção do operador.
create aceita --owner, --depends-on, --files-hint, --acceptance e
--branch. Repita as opções de lista para adicionar vários valores. update aceita
--title, --owner, --depends-on, --files-hint, --acceptance,
--branch e --status; use --clear-owner ou --clear-branch para definir
campos anuláveis como null. Atualizações genéricas não podem mutar uma concessão ativa;
use os comandos de concessão. Valores de lista também podem ser separados por vírgula.
Os status das tarefas são open, in_progress, blocked, done e cancelled.
done e cancelled são terminais. As transições permitem open -> in_progress |
blocked | cancelled, in_progress -> open | blocked | done |
cancelled, e blocked -> open | in_progress | cancelled. Gravações
do mesmo estado são intencionalmente idempotentes. task done move in_progress (ou
done idempotente) para done; task block move open, in_progress ou
blocked para blocked; e task release move blocked, in_progress ou
open para open. Uma tarefa pode avançar para in_progress ou done apenas quando
cada ID em depends_on tem um registro de tarefa cujo status é done. A prontidão é
calculada a partir de registros JSON de tarefa, nunca do texto da mensagem.
Falhas de tarefa e concessão são não zero (status de saída 2) e incluem um código estável.
Os códigos de tarefa incluem TASK_INVALID_COMMAND, TASK_INVALID_ARGUMENT,
TASK_INVALID_CHANNEL, TASK_CHANNEL_NOT_FOUND, TASK_NOT_FOUND,
TASK_ALREADY_EXISTS, TASK_INVALID_STATUS, TASK_INVALID_UPDATE,
TASK_INVALID_TRANSITION, TASK_DEPENDENCY_NOT_READY,
TASK_UNKNOWN_DEPENDENCY, TASK_DEPENDENCY_CYCLE,
TASK_PATH_OUTSIDE_WORKSPACE, TASK_LOCK_TIMEOUT, TASK_IO_ERROR e
TASK_AUDIT_FAILED. Os códigos de concessão incluem LEASE_CONFLICT,
LEASE_RECOVERY_REQUIRED, LEASE_OWNER_MISMATCH, LEASE_NOT_EXPIRED,
LEASE_NOT_FOUND, LEASE_INCONSISTENT, LEASE_MUTATION_REQUIRED,
LEASE_INVALID_OWNER, LEASE_INVALID_TASK_ID, LEASE_INVALID_CHANNEL,
LEASE_INVALID_REASON, LEASE_INVALID_DURATION, LEASE_INVALID_TIMESTAMP,
LEASE_INVALID_RECORD, LEASE_REQUIRED_FIELD_MISSING, LEASE_UNKNOWN_FIELD,
LEASE_RECORD_ID_MISMATCH, LEASE_STORAGE_INVALID,
LEASE_PATH_OUTSIDE_WORKSPACE, LEASE_TRANSACTION_PENDING,
LEASE_TRANSACTION_CLEANUP_FAILED, LEASE_TRANSACTION_PUBLICATION_UNKNOWN,
LEASE_TRANSACTION_INVALID, LEASE_TRANSACTION_NOT_FOUND,
LEASE_TRANSACTION_RECOVERY_FAILED,
LEASE_AUDIT_FAILED e LEASE_AUDIT_ROLLBACK_FAILED. As gravações de concessão usam um
marcador de transação durável com fases preparada/aplicada/publicada; após uma
falha ou falha de limpeza, o acesso falha fechado até que
task recover-pending/LeaseStore.recover_pending() faça explicitamente rollback
ou finalize a transação publicada.
Os bloqueios de caminho coordenam acesso exclusivo a arquivos e diretórios relativos ao workspace:
chat.py lock <channel> <paths...> --as <agent> [--lease-seconds 300]
chat.py check <channel> <paths...> [--as <agent>]
chat.py unlock <channel> <lock-id-or-path> --as <agent>
chat.py recover <channel> <lock-id-or-path> --as <agent> \
--reason "sessão obsoleta" [--lease-seconds 300]
chat.py recover-pending <channel> --as <agent> \
[--resolve-publication rollback|published]
lock e recover aceitam --lease-seconds, --lease ou --ttl. O alvo pode
ser o ID do bloqueio ou o caminho normalizado exato. A normalização resolve
caminhos relativos ao workspace, previne escapes de traversal e root/canal/symlink,
aplica regras de maiúsculas/minúsculas da plataforma (insensível a maiúsculas/minúsculas no Windows) e rejeita
nomes reservados do Windows, pontos/espaços finais, caracteres de controle e pares solitários.
Colisões arquivo/arquivo e sobreposições diretório/arquivo entram em conflito. Apenas o proprietário pode
desbloquear um bloqueio ativo; bloqueios expirados requerem recover explícito registrando
previous_owner, previous_expires_at e recovery_reason. As mutações usam
publicação de arquivo com atomicidade de conteúdo, bloqueios de arquivo consultivos e diário de transações seguro contra falhas.
Erros de bloqueio de caminho saem com código não zero e códigos estáveis como
PATH_LOCK_INVALID_PATH, PATH_LOCK_PATH_OUTSIDE_WORKSPACE,
PATH_LOCK_CONFLICT, PATH_LOCK_RECOVERY_REQUIRED,
PATH_LOCK_OWNER_MISMATCH, PATH_LOCK_NOT_FOUND, PATH_LOCK_NOT_STALE,
PATH_LOCK_INVALID_OWNER, PATH_LOCK_INVALID_LOCK_ID,
PATH_LOCK_INVALID_CHANNEL, PATH_LOCK_INVALID_REASON,
PATH_LOCK_INVALID_DURATION, PATH_LOCK_INVALID_TIMESTAMP,
PATH_LOCK_INVALID_RECORD, PATH_LOCK_STORAGE_INVALID,
PATH_LOCK_STORAGE_ERROR, PATH_LOCK_TIMEOUT,
PATH_LOCK_TRANSACTION_PENDING, PATH_LOCK_TRANSACTION_CLEANUP_FAILED,
PATH_LOCK_TRANSACTION_INVALID, PATH_LOCK_AUDIT_FAILED e
PATH_LOCK_AUDIT_ROLLBACK_FAILED.
O resumo do estado do canal e a compactação derivam um artefato state.md determinístico:
chat.py state <channel> [--write] [--json] [--strict]
chat.py compact <channel> [--as <agent>] [--no-audit] [--json] [--strict]
state renderiza ou imprime o resumo do canal derivado contendo:
- Meta / Tópico (de
_meta.json) - Decisões (extraídas de mensagens e registros estruturados)
- Tarefas Abertas (de
tasks/*.json, incluindo status, proprietário, concessão, dependências) - Bloqueadores (bloqueadores de dependência de tarefa, status de tarefa bloqueada e mensagens de bloqueio)
- Proprietários e Concessões (reivindicações ativas, propriedades de tarefa e bloqueios de caminho)
- Bloqueios de Caminho (bloqueios de caminho ativos de
locks/*.json) - Última Evidência de Verificação (critérios de aceitação e mensagens de verificação)
compact escreve o resumo derivado em <root>/<channel>/state.md usando substituição
atômica de irmão temporário (.state.md.tmp.*) e, por padrão, posta um evento de mensagem
state.compacted auditável no canal.
state.md é estritamente um artefato derivado e nunca é autoritativo.
A compactação não é destrutiva: nunca exclui, muta ou trunca nenhum arquivo de mensagem,
registro de tarefa, reivindicação ativa, bloqueio de caminho ou cursor de leitura. Todos os registros de origem permanecem a
verdade fundamental imutável.
Erros de estado saem com código não zero e códigos estáveis como STATE_INVALID_CHANNEL,
STATE_CHANNEL_NOT_FOUND, STATE_INVALID_RECORD, STATE_MALFORMED_SOURCE,
STATE_RECORD_NOT_FOUND, STATE_IO_ERROR e STATE_AUDIT_FAILED.
--reply <seq> encadeia uma mensagem a uma anterior. python chat.py <cmd> --help para todas as flags.
Comandos de eventos neutros de adaptador:
chat.py event post <channel> --from <agent> --type capability --harness <harness>
chat.py event post <channel> --from <agent> --type status --harness <harness> --status ready
chat.py event read <channel> [--type capability|status]
O corpo do evento é JSON versionado validado contra
schemas/agent-chat-event.schema.json. Mensagens Markdown comuns permanecem
inalteradas e são ignoradas por event read.
Layout de pastas
<root>/
review/ # um canal = um chat em grupo
_meta.json # membros, tópico, criado em
state.md # resumo do estado derivado (não autoritativo)
0001-alice-schema-draft.md # NNNN-<from>-<slug>.md, frontmatter + corpo
0002-bob-schema-review.md
tasks/ # registros de tarefa estruturados
claims/ # registros de concessão ativos
locks/ # bloqueios de caminho do workspace ativos
.cursors/bob.txt # cursor "último seq lido" por agente
deploy/ # um SEGUNDO chat em grupo, espaço de seq separado
...
Messa
…
[truncado para tradução]