Skip to main content
Not every step needs a model. A test suite, a build, a lint, a check against a deployed system: each is a command, and a command is a node like any other. It costs no tokens, it cannot be argued with, and its result can do two things an agent’s cannot be trusted to do alone: send the work back to the step that owns it with the evidence attached, or decide which path the run takes next.

The file

Everything here runs offline. The writer is a small function that gets add wrong once, so the kickback has something to do; in a real team it is an agent seat. The tests and the size check are commands.

What it printed

The two forms

  • A command that sends work back. gateJob(label, commandSucceeds(...), { target }) runs the command; when it exits non-zero, the step returns a revision request aimed at target, carrying the command’s captured output as the finding. The writer at target runs again with that output in hand, and the steps that depend on it run again. No agent reads the test output to decide that; the exit code did.
  • A command that decides the path. Two steps both need the command node, and each carries a when predicate that reads its outcome through ctx.needs: one runs when the tests passed, the other when they failed. The step whose predicate is not met is skipped and recorded as skipped; the graph stays a graph.
ctx.needs is the outcomes of the steps this node needs, by name, the same word the node’s own needs list uses for them. It is present inside a dag and undefined anywhere else.

Gotchas

  • A kickback and a decision are different tools. Send work back when the same writer should fix it; decide the path when a different step should take over. The example shows both so the difference is visible.
  • A skipped branch is green. An unmet when records a skip and does not fail the run. If a branch must run, do not give it a when.
  • The kickback budget is per target. maxKickbacks as a map gives the writer its own count of returns; when it is spent, the run fails with the last output as its reason rather than looping.

Source

The file is examples/command-kickback.ts. gateJob, commandSucceeds, predicate and dag are exported by @obversa/runtime.