Skip to content

No advisory deterministic step: v2 gates every deterministic step on exit code, so repair-before-failure flows all re-invent '|| true' #509

Description

@khaliqgant

Context

v2 gates every deterministic step on exit code with no opt-out. v1 had failOnError: false, and it existed for one dominant pattern: run a check, let it be red, and hand its output to the agent built to answer it. That pattern — repair before failure — is the whole shape of a validated implementation flow, and in v2 every author has to re-invent a workaround for it.

Found while porting AgentWorkforce/relay's 80-to-100 validation workflows to v2. The repo's own v1-to-v2 bridge documents the workaround in flows/spec-builder.ts:

| v1                   | v2                    |
| `failOnError: false` | `<command> || true`   |

with this comment, which is the tell:

The group braces are load-bearing: || cannot begin a line, so appending "\n|| true" to a multi-line command is a shell syntax error rather than a fallback.

flows/diagnose/orchestration.spec.ts carries the same workaround under the name advisoryGate.

Why || true is not good enough

It discards the exit code. A later gate then has nothing to read, so the flow has to re-run the command to learn what happened, or the author builds an evidence journal outside the kernel. Authoring the relay native-delivery campaign I ended up writing exactly that: a recorder script that runs each command, writes {command, exitCode, verdict, tail} to disk, and always exits 0, plus a require-green gate that reads those files back. That is the kernel's job — every one of those facts is already journal-shaped.

There is a second, worse failure mode: || true is indistinguishable from success, so a flow that forgets one gate ships red work silently.

What to change

Give the deterministic step a first-class non-fatal mode. The shape that fits v2's journal-first design:

- id: run-tests
  type: deterministic
  command: npm test
  onNonZero: record     # 'fail' (default) | 'record'

record journals the exit code and output as a step outcome, marks the step complete-but-red, and lets dependents run. A later gate reads the recorded outcome — { type: 'steps_green', ids: [...] } or equivalent — rather than re-running the command.

In TypeScript, the natural form is a result object instead of a throw:

const tests = await f.run('npm test', { onNonZero: 'record' });
if (!tests.ok) await f.agent('fixer', { task: `Fix these failures:\n${tests.output}` });

Acceptance

  • A deterministic step can be red without failing the run, and its exit code and output are readable by a later step from the journal.
  • A gate can assert "these recorded steps were all green" without re-running them.
  • || true is no longer the documented answer; the repair-before-failure pattern appears in the docs as a supported shape.

Out of scope

  • Retry policy (maxIterations).
  • Agent-step failure semantics.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgarden-readyScoped and ready for an agent to pick up

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions