Skip to content

Instantly share code, notes, and snippets.

@renezander030
Created June 12, 2026 16:07
Show Gist options
  • Select an option

  • Save renezander030/2f0754a4babd185d22d8498d5dc04982 to your computer and use it in GitHub Desktop.

Select an option

Save renezander030/2f0754a4babd185d22d8498d5dc04982 to your computer and use it in GitHub Desktop.
AI agents that can't pick their own next action: deterministic step pipelines in Go

AI agents that can't pick their own next action: deterministic step pipelines in Go

Build AI automation where the LLM fills one typed step and the engine walks a fixed sequence — so an agent can't choose to do something you didn't plan. About one switch statement.

Last tested: June 2026. See Changelog at the bottom.

If this saves you setup time, follow @renezander030 — production notes on shipping AI agents outside demos.

Reference implementation (Go, MIT): github.com/renezander030/draftcat

TL;DR cheat sheet

Question Answer
Does the agent pick its next action? No. The engine walks a fixed list of typed steps.
What does the LLM actually do? Fills one ai step: structured output, schema-validated, budget-checked.
How do side effects happen? Only through a deterministic step (plain Go) or a human approval step.
What happens on a bad step? It errors and the pipeline halts — no "the agent retried something weird."

The rule of thumb

An autonomous agent loops "think, choose a tool, act" and chooses the next action itself. That is exactly the part you cannot audit. Flip it:

  1. The pipeline is a fixed sequence of typed steps, declared up front — not chosen at runtime.
  2. Three step types only: deterministic (plain Go), ai (LLM inference, schema-validated), approval (human gate).
  3. The LLM never selects what runs next. It produces structured output for its one step; the engine decides the order, because the order is hard-coded in config.

Recommended setup

Declare the pipeline as a fixed list of steps in YAML. The LLM shows up at exactly the steps you marked ai, and nowhere else.

1. A pipeline is a fixed step list (steal-able config)

pipelines:
  - name: invoice-due-diligence
    schedule: 1h
    steps:
      - {name: parse-pdf, type: deterministic, action: pdf_extract}
      - {name: extract,   type: ai,            skill: extract-line-items}
      - {name: verify,    type: deterministic, action: pdf_verify_cite}
      - {name: review,    type: approval,      channel: telegram}

The order is in the file, not in the model's head. Swap, reorder, or delete a step by editing config, never by re-prompting.

2. The step type (Go)

type StepConfig struct {
    Name   string `yaml:"name"`
    Type   string `yaml:"type"`   // deterministic, ai, approval
    Action string `yaml:"action"` // deterministic action name
    Skill  string `yaml:"skill"`  // reference to skills/<name>.yaml
}

3. The engine walks the list; one switch is the whole control flow

for _, step := range pipeline.Steps {
    switch step.Type {
    case "deterministic":
        // plain Go: fetch, parse, dedup, write. No model involved.
    case "ai":
        // LLM inference with a skill template; output is schema-validated
        // and budget-checked before it's allowed to continue.
    case "approval":
        // pause and wait for a human to approve / edit / reject.
    }
}

There is no branch where a model returns "next, call tool X." The loop is the authority; the model is a typed function inside one case.

Why deterministic steps instead of an autonomous agent

  • Auditable. The run is the step list plus each step's output. You can diff two runs; you cannot diff two free-roaming agent loops.
  • Testable. A fixed pipeline dry-runs against fixtures (draftcat test <pipeline>). An agent that picks its own path doesn't have a stable path to test.
  • Bounded blast radius. A wrong LLM output is contained to its step's schema; it can't decide to call a delete tool you never wired in.
  • Still flexible where it matters. The content is LLM-driven (the ai step), the control flow is not. You keep the writing, lose the roulette.

Use an autonomous agent when exploration is the point. Use a fixed step pipeline when a wrong choice emails a real customer.

Smoke test

$ draftcat test invoice-due-diligence
[step parse-pdf] deterministic ok
[step extract]   ai ok (schema valid, 812 tok)
[step verify]    deterministic ok
[step review]    approval — would prompt operator (dry-run)

Pass criteria: every step runs in declared order, the ai step's output validates against its schema, and the approval step pauses rather than auto-sending.


Series

This is Production AI Automation Notes #10. The series, on shipping AI agents outside demos:

  1. Agent Approval Gates — the per-step approval contract
  2. Token Budgets
  3. SQLite dedup + crash safety
  4. Prompt-injection defense
  5. PDF cite verification
  6. LLM cost tracking in Go
  7. Deterministic step pipelines (this entry)

Reference implementation for the Go entries: draftcat (MIT).

Follow @renezander030 for new entries.

Sources

Reader contributions

Running fixed pipelines vs an autonomous loop? Comment with your step types, how you validate the ai step output, and where you draw the line between "let the model choose" and "the config chooses."

Changelog

2026-06-12

  • Initial entry. Skipped the hardware-by-model matrix (not hardware-bound) and a new companion repo (draftcat already carries the runnable engine).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment