Passo de raciocínio, e uma retomada que confere o que o humano respondeu - #16
Merged
Merged
Conversation
…isa ser confiável
A camada de Apps do Workbench (PROD-2127) tem passos cujo resultado é
estrutura, não arquivo: um dossiê de pesquisa, uma lista de caminhos
criativos, um diagnóstico. O runner não sabia representar isso.
`ProviderOutput` só aceita `url` ou `data` (provider_base.py:25-32), e
`_run_step` assume que todo passo salva arquivo. Passo de raciocínio não
podia virar generation com asset falso só para caber nesse caminho: asset
draft acende "precisa de você" no kanban e infla a contagem de entrega.
Entra o kind `reason`: grava a estrutura em `runs.state`, grava uma linha em
`generations` e nenhum asset. A linha entra no ledger por três motivos
medidos: enquanto está `pending` o card acende "em geração", o que cobre
espera longa de motor; `created_at`/`finished_at` dão latência por passo, que
não existia em lugar nenhum; e `cost_brl` ganha onde morar quando o custo de
token passar a ser medido. O zero vem marcado `cost_source: nao-apurado`,
porque zero sem procedência é o erro que a apuração de junho já cometeu.
Três consertos na retomada, todos encontrados ao tentar provar o mecanismo:
1. `resume` herdava `session_id` do parâmetro, e o CLI passava `None`
(cli.py:939). Com `session_id or 0` isso violava a FK de
`generations.session_id` e a run inteira virava `failed` na primeira
geração depois da pausa. Reproduzido: `FOREIGN KEY constraint failed`.
Agora o `session_id` vem da linha da run, que é quem sabe.
2. A escolha do humano era gravada crua, sem nenhuma checagem. Qualquer
`--selected` era aceito, inclusive um que nunca esteve nas opções, e o
passo seguinte consumia um valor que nunca esteve no estado. A pausa passa
a persistir as opções, e a retomada resolve a resposta contra elas:
`'inventada' não está entre as opções da pausa em 'escolha'`. Opção
escalar continua devolvendo o dicionário do humano, para
`{{ steps.pick.selected }}` seguir resolvendo.
3. Falha de step apagava o ponteiro e o trabalho já pago. Um 429 do provider
depois de uma escolha humana perdia a escolha. `step_outputs` continua
preservado e a run falhada passa a ser retomável sem repetir a pergunta.
Mais: a pausa acende `sessions.awaiting_input`, que é o que faz o card ir
para "precisa de você" no Workbench; `from` string vira uma opção, não uma
por caractere; passo de mídia que declara output de estrutura falha alto em
vez de descartar em silêncio; `workflow save` respeita o `slug` do YAML em
vez de derivá-lo do `--name`; e `run`, `workflow list` e `workflow show`
ganham `--json`, que é como o Workbench lê o mesmo arquivo sem ponte.
`studiolocal runs [--paused]` é novo e existe porque o `run_id` só vivia no
stdout de quem disparou: sem ele, run pausada era invisível depois do
terminal fechar.
CI: primeiro workflow deste repositório. Gate é pytest mais o subconjunto do
ruff que pega defeito (E9, F). Os 23 achados de estilo herdados ficam
declarados em voz alta num passo não bloqueante, porque seis deles pedem
renomear exceção pública.
Provado no data root de desenvolvimento do Workbench, com geração real:
run 1 pausou em `escolha`, o processo morreu, a retomada de processo frio
recusou `{"id":"inventada"}` e aceitou `{"id":"chuva"}`, e o prompt gravado
saiu "uma foto de jabuticaba sob chuva, tom neon (eco de jabuticaba)", com as
duas metades vindas de `runs.state`. Asset 1024x1024, R$ 0,68, sessão aberta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
O Python do ubuntu-latest é externally managed, e `uv pip install --system` sai com exit 2 antes de instalar qualquer coisa. Passa a criar venv e rodar os três passos com `uv run`, da raiz do repositório, que é onde o editable resolve `from lib.X`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 22, 2026
This was referenced Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Metade do esqueleto da Etapa 1 do Crystal Ball (Linear PROD-2127). A outra metade é a PR do Workbench, que consome o
--jsonque esta PR cria.O problema
Um app declarado tem passos cujo resultado é estrutura, não arquivo: um dossiê de pesquisa, cinco caminhos criativos, um diagnóstico. O runner não sabia representar isso.
ProviderOutputsó aceitaurloudata(lib/provider_base.py:25-32) e_run_stepassume arquivo em todo passo. E passo de raciocínio não podia virar generation com asset falso só para caber: assetdraftacende "precisa de você" no kanban do Workbench e infla a contagem de entrega.O que entra
Kind
reason: grava a estrutura emruns.state, uma linha emgenerations, zero assets. Ocost_brl=0.0vem comcost_source: nao-apurado, porque zero sem procedência já produziu prejuízo antes.Três consertos na retomada, todos encontrados ao tentar provar o mecanismo, e nenhum deles aparecia em teste porque não existe uma única run em nenhum banco desta máquina:
resumeusava osession_iddo parâmetro, e o CLI passavaNone(lib/cli.py:939). Comsession_id or 0, a FK degenerations.session_idera violada e a run inteira viravafailedna primeira geração depois da pausa.--selectedpassava, inclusive um que nunca esteve nas opções. Agora a pausa persiste as opções e a retomada resolve a resposta contra elas.step_outputssobrevive e a run falhada é retomável.Mais: a pausa acende
sessions.awaiting_input;fromstring vira uma opção, não uma por caractere; passo de mídia que declara output de estrutura falha alto;workflow saverespeita oslugdo YAML;run,workflow listeworkflow showganham--json; estudiolocal runs [--paused]é novo, porque orun_idsó vivia no stdout de quem disparou.Verificação
pytest -q, na raiz do repo:Sendo 7 novos em
tests/test_workflow_app_steps.pye 187 que já existiam, nenhum alterado. O gate do ruff:Os 23 achados de estilo herdados continuam 23 (medido antes e depois com
git stash): esta PR não acrescenta nenhum. Ficam num passo não bloqueante e declarado, porque seis deles pedem renomear exceção pública, o que é mudança de API e não cabe aqui.Ponta a ponta, com geração real, no data root de desenvolvimento do Workbench
Disparo:
A pausa, como o kanban a lê:
awaiting_input=1comrodando=0edrafts=0é a combinação que fazderiveColumnacenderprecisa_vocepelo gatilho certo.column_statuscontinuapor_fazerde propósito: a coluna é derivada, e o app nunca escreve nela.Processo frio, e a recusa:
Retomada válida:
E o que ficou gravado:
O prompt é a prova que interessa. A retomada recebeu só
{"id":"chuva"}: as duas metades do texto, a frase da opção e a palavrajabuticaba, vieram deruns.state, de um processo que já tinha morrido.session_id=2em vez de0é o conserto 1 funcionando, e antes dele a linha nem existiria.O passo de raciocínio no ledger:
E a sessão não fechou ao gerar (
closed_atnulo), que é o comportamento do Workbench e não o do CMS.O que fica de fora, e vira issue
generationpendente: se o processo morrer durante um passoreasonlongo, o card fica preso em "em geração".docs/DESIGN.md:554já registrava).workflow validatenão existe: a validação continua sendo só o prefixo doschema.crystalballnão entra aqui; o v1 doreasonsó conhecetemplate. A Etapa 2 resolve.🤖 Generated with Claude Code