Skip to content

Onda 1–3: RunTool ativado — squad como executor real - #48

Merged
danzeroum merged 4 commits into
mainfrom
claude/tool-execution-architecture-y1gm5j
Jul 7, 2026
Merged

Onda 1–3: RunTool ativado — squad como executor real#48
danzeroum merged 4 commits into
mainfrom
claude/tool-execution-architecture-y1gm5j

Conversation

@danzeroum

Copy link
Copy Markdown
Owner

Resumo

Ativa RunTool no CoreService (Rust), implementa o loop ReAct real no DeveloperAgent (Python) e adiciona um gate duro no auditor para validar evidência real de execução de ferramenta. Fecha as Ondas 1–3 da "tool execution architecture" (ADR 0023): forge squad "crie X.html..." agora produz X.html de verdade no workspace, registrado no ledger, com o auditor julgando sobre artefato que existe — não sobre alegação de texto.

Mudanças principais

Onda 1 — Executor real no Rust

  • crates/forge-cli/src/squad.rs: novo helper core_run_tool compartilhado pelos três CoreBackend de produção (GatewayCoreBackend, WebSquadCoreBackend, ScriptedSquadCoreBackend). Recalcula escopo real via Tool::scope (o ToolCall.scope da rede é só informativo — nunca decide permissão), avalia via PermissionEngine (perfil BUILD reutilizado), executa em spawn_blocking e registra no ledger via novo session::append_entry (variante sem override).
  • crates/forge-cli/src/session.rs: refatora append_override_entry em append_entry_impl genérico; novo append_entry público para logging fora do ciclo de vida de Session.
  • crates/forge-sidecar/src/core_server.rs: implementa CoreBackend::run_tool (antes Unimplemented); handler gRPC é passthrough fino.
  • crates/forge-sidecar/src/service.rs: testes ganham stub de run_tool.
  • crates/forge-cli/src/squad_agent.rs: WebSquadCoreBackend ganha root/tools/tool_permissions e chama core_run_tool.
  • Convenção de exit_code: 0 sucesso; 1 erro de execução/args inválidos/ferramenta desconhecida (vale tentar de novo); -1 negado (não adianta repetir) — sinal estrutural para o loop ReAct.

Onda 2 — Loop ReAct real no DeveloperAgent (Python)

  • python/packages/forge-squad/src/forge_squad/agents/developer.py: novo _implement_with_tools é o loop ReAct real. O modelo alterna entre tool_call (executado de verdade via ToolClient) e final_answer, iterando até um dos dois ou até estourar _MAX_REACT_STEPS/_REACT_TIMEOUT_SECONDS. Sinal de ativação: bool(task.get("action")) and tool_client is not None — separa trabalho real do plano de proposta/avaliação.
  • python/packages/forge-squad/src/forge_squad/tool_client.py (novo): contrato ToolClient desacoplado do transporte gRPC (mesmo padrão de GatewayClient/PermissionClient). ScriptedToolClient para testes.
  • python/packages/forge-squad/src/forge_squad/grpc_clients.py: novo GrpcToolClient sobre CoreService.RunTool (stub Python já existia, nunca usado).
  • python/packages/forge-squad/src/forge_squad/orchestrator.py: UnifiedOrchestrator.__init__ ganha tool_client opcional; só o developer o recebe (attach_tool_client).
  • python/packages/forge-squad/src/forge_squad/server.py: injeta `G

https://claude.ai/code/session_01K6H8cTKxEw8uPHW8L38apx

claude added 3 commits July 7, 2026 16:50
CoreBackend ganha run_tool (core_server.rs); o handler gRPC vira um
passthrough fino em vez de Unimplemented — RunTool já existia no proto,
zero mudança breaking. core_run_tool (squad.rs) recalcula o escopo real
via Tool::scope (o ToolCall.scope da rede é só informativo, nunca decide
permissão), avalia via PermissionEngine (perfil BUILD), roda a ferramenta
em spawn_blocking e registra squad.tool_run no ledger.

Os três CoreBackend de produção (GatewayCoreBackend, WebSquadCoreBackend,
ScriptedSquadCoreBackend) ganham ToolRegistry/PermissionEngine e delegam
a core_run_tool, cada um reusando sua própria ponte HITL já existente.

Testes de fronteira (core_server_inprocess.rs, sem Python): um ToolCall
via UDS puro cria um arquivo real no disco; uma negação do motor de
permissões não executa nada.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6H8cTKxEw8uPHW8L38apx
…verdade

tool_client.py (novo) + GrpcToolClient (grpc_clients.py): ToolClient
Protocol sobre CoreService.RunTool — zero codegen novo, o stub Python já
existia gerado e nunca usado.

developer.py: _implement_with_tools é o loop ReAct real — o modelo
alterna entre tool_call (executado via tool_client, sob o motor de
permissões do Rust) e final_answer, até um dos dois ou até estourar
_MAX_REACT_STEPS/_REACT_TIMEOUT_SECONDS (honesto: "incomplete", nunca
fabrica sucesso). Sinal de ativação: bool(task.get("action")) — separa
trabalho real do plano de proposta/avaliação sem tocar o caminho de
chamada única existente.

Achado real ao revisar o plano antes de implementar: _can_parallelize
manda um passo "implement" sem dependencies (o caso comum) para
_extract_parallel_tasks, que chamava o developer SEM "action" — o sinal
de ativação nunca disparava nesse caminho, reproduzindo o bug original
mesmo com RunTool/ReAct prontos. Fix: _extract_parallel_tasks propaga
action/prior_results (mesma forma do step_task sequencial). Regressão
coberta por teste que falha sem o fix (verificado manualmente revertendo
e rodando antes de reaplicar).

core_generate (squad.rs) ganha o papel "assistant" (Role::Assistant) —
sem isto, o histórico multi-turno do loop ReAct colapsava tudo em
Role::User, e a API da Anthropic exige alternância estrita.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6H8cTKxEw8uPHW8L38apx
…ADR 0023

auditor.py: _claims_completion_without_write_evidence é um gate duro
(mesma filosofia de forge_review/gates.py::evaluate) — reprova "completed"
sem tool_calls de escrita bem-sucedida ANTES do gateway ser chamado. Só se
aplica a resultados que passaram pelo loop ReAct (carregam a chave
tool_calls, mesmo vazia); um resultado do caminho antigo de chamada única
nunca teve infraestrutura de ferramenta disponível, então não é gateado —
achado real ao rodar a suíte (sem essa distinção, o gate quebrava um
teste legítimo da Onda 0 que usa o caminho antigo de propósito).

orchestrator.py: o veredito final vira observável via um novo StepResult
(step_id: "final_validation") — sem isto, "o auditor julga sobre o
arquivo real" não seria verificável fora do dict de retorno que
server.py descarta.

Teste de fechamento (squad_e2e.rs, processo Python real, sem key):
forge squad "crie scientific-calculator.html..." produz o arquivo de
verdade no workspace, registra squad.tool_run no ledger, e o auditor
aprova sobre evidência real — o backend roteirizado falha alto e claro
(assert dentro do generate()) se o payload que chega ao Rust não
carregar tool_calls, provando que o auditor não julga no vácuo.

ADR 0023 documenta a decisão completa (Ondas 1-3) e os dois achados reais
de implementação (caminho paralelo do orquestrador, papel "assistant" em
core_generate). pendencias.md fecha o gap remanescente registrado na
Onda 0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6H8cTKxEw8uPHW8L38apx
@danzeroum danzeroum closed this Jul 7, 2026
@danzeroum
danzeroum deleted the claude/tool-execution-architecture-y1gm5j branch July 7, 2026 22:14
@danzeroum
danzeroum restored the claude/tool-execution-architecture-y1gm5j branch July 7, 2026 22:14
@danzeroum danzeroum reopened this Jul 7, 2026
@danzeroum
danzeroum merged commit ea21452 into main Jul 7, 2026
6 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants