← Back to blog

Deterministic chaining: skip what the LLM doesn't need to decide

You have a deploy pipeline. Lint, test, build, then deploy. The first three steps don't need judgment. They either pass or they don't. But without chaining, the LLM has to babysit every single one.

The round-trip problem

Here's what happens without chaining. The model starts the workflow. Gets back the lint step. Reads the response. Reasons about it. Picks the transition. Submits it. Waits for lint to finish. Gets the result. Reasons again. Picks the next transition. Submits. Waits for tests...

That's three full LLM round trips for steps where there's nothing to decide. Each round trip means:

  • Input tokens to re-read the workflow state
  • Output tokens to reason about the "choice"
  • Network latency for each call
  • API cost that scales with conversation length

For a 10-step pipeline where 8 steps are computable, that's 8 unnecessary round trips. Real money, real latency, zero value.

Tag it and forget it

The fix is one field: actor: "deterministic".

deploy-pipeline.yaml
lint:
  transitions:
    run_lint:
      target: test
      actor: deterministic
      executor: { kind: cli, command: lint-check }
test:
  transitions:
    run_tests:
      target: build
      actor: deterministic
      executor: { kind: cli, command: test-runner }
build:
  transitions:
    build_artifact:
      target: ready_to_deploy
      actor: deterministic
ready_to_deploy:
  goal: Confirm deployment
  guidance: All checks passed. Review before deploying.
  transitions:
    deploy:
      target: deployed
      actor: agent

The model calls workflow.start. The runtime sees that the first state has only deterministic transitions. It executes lint. Succeeds. Moves to test. Executes. Succeeds. Moves to build. Executes. Succeeds. Arrives at ready_to_deploy — which has an agent transition. The chain stops.

Three executor calls. Zero LLM round trips. One response back to the model with the full chain trace.

What the model gets back

The response includes everything the model needs to make the deploy decision:

  • A chain array tracing each auto-executed step and its result
  • A guidance object with the goal and instructions for the current state
  • Links to the available transitions — in this case, just "deploy"

The model reads the lint results, test results, build output. It has context for the decision. And it didn't waste tokens getting here.

When things break mid-chain

If the test step fails, the chain stops at the failure. The response includes:

  • The partial chain trace — lint succeeded, tests failed
  • The error from the failed executor
  • A recovery link so the model can retry just the failed step

The model doesn't have to restart the whole pipeline. It sees what worked, what broke, and has a link to try again.

The safety net

Chains have a depth limit: maxChainDepth, default 50. If your workflow has a cycle of deterministic transitions (it shouldn't, but mistakes happen), the chain stops at the limit instead of running forever.

You can set it per workflow:

workflows:
  deploy:
    maxChainDepth: 20
    states: ...

When to use it

Any step where the outcome is computable — not a judgment call — is a candidate. Linting, testing, building, data validation, format conversion, notification sending. If a human wouldn't need to think about it, the model doesn't either.

The model's time is expensive. Spend it on decisions that matter.

See the full chaining guide or explore the deploy-pipeline example.