Workflow (Agent DAG/State Machine): foundations

accepted

Status

accepted — 2026-05-09. Sector bootstrap (skeleton + 5 impl tickets) landed as part of /k-go services/ai audit wave (Modo C). Q1 resolved: YAML is the wire format (declarative graphs, lint-friendly, diff-friendly), Go SDK engines/sdk/go/workflow provides typed step authoring (PythonDart bindings follow as needed). Q2 resolved: separate sectors with explicit boundary — `servicesfoundationbpm` handles BPMN-style humanform-driven business process; services/ai/workflow handles agent-heavy execution; cross-sector calls allowed (subflow/subprocess).

Summary

DAGstate machine pra agents complexos — análogo LangGraphInngestTemporal AIRestate.

Motivation

agents/ é orquestração de agents singulares. Workflows multi-agent / multi-step com retry/state ainda não tem foundation. Bloqueia produção.

Scope

In

  • DAG definition (YAML/code)
  • State persistence
  • Retry policies
  • Branching
  • Human-in-the-loop steps

Out (yet)

  • Full BPMN (escopo services/foundation/bpm)
  • Pipeline ETL

Initial design

Surfaces

  • backend/ — Go API + state machine engine
  • app/web — UI minimal de runs (futuro)

Key APIs

  • POST /v1/workflows — registrar
  • POST /v1/workflows/{id}/runs — disparar run
  • GET /v1/workflows/{id}/runs/{run_id} — fetch state

Dependencies

  • services/ai/agents — actors
  • services/ai/trace — observability
  • infra/data/kdb-doc — state
  • services/ai/sandbox — steps que rodam código

Relation to existing sectors

  • Camada acima de agents
  • Cooperativo com sandbox e trace

State mutation model (resolved 2026-05-25 — owner-ratified, workflow#010)

O guard scope é {state, steps, inputs} (engine evalScope, #008): guards/ expressões CEL leem inputs.* (inputs iniciais), steps.X.output (output de cada step concluído) e state.* (vars de estado). A escrita de state.* por um step usa output_to explícito (não merge implícito, não reducers):

  • Campo opcional output_to: <ident> no step. Ao concluir com sucesso, o

    engine faz state[output_to] = output antes de avaliar os guards das edges de saída (a própria edge do step pode rotear na var). Sem output_to, o step não publica state var (comportamento N/N-1 inalterado — wire-format aditivo estável per always-on.kmd).

  • Lint valida output_to como identificador bare e rejeita os roots

    reservados (statestepsinputs) — WF-INVALID-OUTPUT-TO.

  • Reducers (set: { var: "<expr CEL>" } — transformar o output antes de

    gravar) ficam diferidos até ≥2 workflows reais precisarem (reuse-first/ hyperscale-first); migração é aditiva sobre output_to.

Descartados: merge implícito do output no topo de state (clobber silencioso, sem controle do autor) e auto-store em state.<stepName> (duplica steps.X.output).

Self-hosted-first analysis (5 gates)

  • G1 Feature parity: zero
  • G2 Performance: N/A
  • G3 Stability: N/A
  • G4 Capability: Temporal-style viável
  • G5 Critical-path readiness: destrava complex agents

Open questions

  • Q1: DSL — YAML, código, ou ambos?
  • Q2: Reuso de services/foundation/bpm se existir?

Next steps

  1. Ratificar esta RFC (1 round de comments).
  2. Criar sector dir services/ai/workflow/ com koder.toml, README.md, skeleton.
  3. Abrir tickets de implementação em services/ai/workflow/backlog/pending/.
  4. Registrar em meta/docs/stack/registries/self-hosted-pairs.md se substituir externo.