Module 23 of 26 · Open Source Track
Agentic Coding
Workflows & Quality Gates

Once an agent can write and commit code, you need scaffolding to trust it: reusable workflow files, a spec→generate→verify framework, structural linting that beats regex, and pre-commit gates that turn a failing check into a fix the agent makes itself.

Track 7: Engineering Practices ⏱ ~16 min read 23 / 26
Concept 1 of 5

Humans review intent; machines enforce the floor

An agent produces plausible-looking code at machine speed and almost never pauses to ask. A human who is unsure leaves a TODO; an agent confidently invents an API, hardcodes a secret to pass a test, or swallows an exception — faster than you can read the diff.

So you stop relying on a human reading every line and put deterministic machine checks in the path: linters, scanners, and commit gates the agent must satisfy. The human reviews whether it's the right change; the machines guarantee it isn't dangerous.

Concept 1 of 5

The tireless junior who never says "I'm unsure"

BEFORE: You have a local Mistral agent that never gets bored and attempts anything at 3 a.m. Ask for a function; seconds later it exists, with tests, looking completely plausible.

PAIN: "Looks plausible" is the failure mode. It hardcodes a password to make a test green and commits "done" — many times an hour, faster than any reviewer keeps up. Code review, the thing that catches a careless human, drowns.

MAPPING: The fix isn't a better reviewer — it's a turnstile. Deterministic gates run in seconds on every commit and reject the unsafe pattern. The agent doesn't get annoyed; it reads the error, fixes it, retries.

Concept 1 of 5

What the machines take over

  1. Catch unsafe patternsA linter rejects eval(), shell=True, and other footguns — deterministically.
  2. Find leaked secretsA scanner flags the hardcoded password the agent added to make a test pass.
  3. Block the bad commitA pre-commit gate refuses to record code that fails the checks — nothing enters history.
  4. Hand back the reasonThe specific error goes to the agent, which fixes exactly that and retries. The loop closes without a human in it.
Concept 1 of 5

Prompt it, or gate it?

# How do I enforce this rule on agent code?

IF it must hold EVERY time
   (security, secrets, irreversible):
  USE a deterministic gate
  # a prompt is a request the model may ignore

ELIF it's a style preference / nudge:
  USE a prompt instruction

ELSE:
  PREFER the gate
  # machines enforce; humans review intent

"Don't commit secrets" in a prompt is probabilistic. A scanner that exits non-zero is a guarantee.

Concept 1 of 5

Misconceptions

"A smarter model needs fewer gates."
Speed is the problem, not intelligence. A capable agent ships more plausible diffs faster, so the review bottleneck gets worse. Gates scale; a human reading every line doesn't.
"Just tell it in the prompt to write safe code."
A prompt is a probabilistic request the model can ignore under pressure. Anything that must hold every time belongs in a deterministic gate.

Agent code needs stricter automated gates, not looser ones. Its speed is exactly why human review can't be the only net.

Humans review intent; machines enforce the floor in seconds, on every commit.

Concept 2 of 5

Treat a good prompt as source code

A reusable workflow file is a prompt-plus-procedure saved to disk as a versioned, parameterized file instead of re-typed into a chat box. It captures a repeated task — "review this diff for security issues" — with named slots ({{diff}}) you fill at run time.

Because it's a file, it lives in your repo, gets code-reviewed, and improves over time. One generic runner loads any workflow file, fills its slots, and runs it — the procedure lives in data, not code.

Concept 2 of 5

A recipe card, not a nightly explanation

BEFORE: You make the same dish often and just explain the steps out loud each time you cook it.

PAIN: Every retelling drifts — a pinch more here, a step skipped there. No two dinners are the same, and you can't hand the method to anyone else or improve it systematically.

MAPPING: Writing it on a recipe card fixes the procedure. Anyone can run it, the result is reproducible, and you refine the card over time. A workflow file is that card: a reviewed, versioned prompt with fill-in slots.

Concept 2 of 5

File in, result out

  1. Frontmatter + bodyMetadata on top (name, model, output contract), then the templated prompt with {{slots}}.
  2. A generic runner loads itSplit frontmatter from body, parse the simple key:value metadata.
  3. Fill the slotsReplace each {{name}} with the supplied parameter; fail loudly if one is missing.
  4. Run & validateCall the model, then validate the output against the file's declared contract (e.g. JSON) — small models drift.
Concept 2 of 5

Pseudocode

# workflows/review.md  (data, not code)
# --- name: security-review, output: json ---
# body: "Review this DIFF... {{diff}}"

DEFINE run_workflow(path, params):
  meta, body = split_frontmatter(read(path))
  prompt = REPLACE each {{slot}} in body
             with params[slot]   # fail if missing
  raw = ASK Mistral(model=meta.model, prompt)
  IF meta.output == "json":
    RETURN parse_json(raw)   # validate the contract
  RETURN raw

# git diff | run_workflow("review.md")
Concept 2 of 5

Misconceptions

"A saved prompt is just a text snippet."
Treat it as source: versioned, parameterized, with a declared output contract a runner validates. That's what makes it reproducible and composable, not just convenient.
"I need a new script for each workflow."
No — one generic runner executes any workflow file. Add a docstring.md or migration.md and the same runner runs it with zero new code.

Separate the procedure (a versioned file) from the executor (one generic runner). Same review runs identically every time.

Prompts-as-files compose, version, and become the unit a framework orchestrates.

Concept 3 of 5

An objective grader drives the loop

A workflow framework orchestrates multi-step coding: it sequences steps, carries state, retries, and decides when to stop. For coding the canonical shape is spec-driven: a written spec defines success, the agent generates code, and an external check — the tests — grades it.

The defining feature is that the grader is objective and outside the model. The tests pass or they don't; the model's opinion of its own work doesn't count. That single choice lets the loop run unattended without drifting into confident nonsense.

Concept 3 of 5

A driving test, not self-assessment

BEFORE: A learner driver practices and feels they're ready.

PAIN: "I feel ready" is not proof — confidence and competence aren't the same. If the learner graded their own test, everyone would pass and the roads would be unsafe.

MAPPING: The framework is an examiner with a fixed checklist (the tests). The learner (the model) keeps practicing — informed by exactly what they failed last time — until the external examiner passes them. A weaker driver can still get there, just in more attempts.

Concept 3 of 5

Spec → generate → verify → iterate

  1. Fix the spec & tests up frontThe spec is what the model reads; the tests are how you grade it. No way to "pass" except real assertions.
  2. Generate, then write to diskStrip prose/fences from the reply and write the solution file. Nothing is trusted yet.
  3. Run the external graderShell out to the test suite in a subprocess with a timeout. The return code — not the model — is the verdict.
  4. Feed the real failure back, boundedAppend the actual test output and retry, capped by max_iters so a stuck model fails cleanly, never forever.
Concept 3 of 5

Pseudocode

FIX spec, tests        # tests = the objective grader
messages = [system, spec]

FOR i IN 0..max_iters:        # mandatory cap
  code = extract(ASK Mistral(messages))
  write("solution.py", code)
  passed, output = RUN tests   # subprocess + timeout
  IF passed: RETURN success
  # inform the next attempt with the real error
  messages += [code, "Tests failed:\n" + output]

RETURN failure         # couldn't do it, cleanly
Concept 3 of 5

Misconceptions

"Ask the model if its code is correct."
Self-grading can be gamed. The grader must be external and objective — a real test run — or the loop drifts into confident nonsense.
"A 7B model failing twice means the loop is broken."
That's exactly why the loop exists. Iterating on real test output turns a weaker model reliable — it just needs a few more attempts. Budget max_iters = 4–6 for small models.

The external test result — not the model's self-assessment — decides success and drives the next step.

Always cap the iterations: a model that can't satisfy the spec must fail loudly, not spin forever.

Concept 4 of 5

Read the code the way the compiler does

A structural linter parses source into an Abstract Syntax Tree (AST) and matches on the shape of a construct — "a call to eval with any argument" — immune to spacing, aliasing, comments, and string look-alikes.

Three free, local tools cover it: ruff (style + likely bugs), bandit (Python security anti-patterns), and ast-grep (your own structural rules). Each exits non-zero on a violation — the contract a gate branches on.

Concept 4 of 5

Spell-check vs Find & Replace

BEFORE: To ban eval() your first instinct is grep "eval(" — a text Find. Works for a day.

PAIN: Find matches characters. It flags self.evaluate() and a comment "don't eval this," and misses ev = eval; ev(x) or eval (x) with a space. Too noisy and too leaky to be a security gate.

MAPPING: A structural linter is spell-check — it understands grammar, not letters. It sees the actual call node however it's spelled, so aliasing and spacing can't evade it and a string can't trigger it.

Concept 4 of 5

Three tools, three jobs

  1. ruff — style + bugsUnused imports, undefined names, mutable default args. Fast, auto-fixes much of it.
  2. bandit — securityHardcoded passwords (B105), shell=True, eval(), try/except/pass (B110).
  3. ast-grep — your policyStructural rules specific to your codebase, e.g. "no swallowed exception."
  4. Chain on exit codesRun all three; any non-zero fails the gate — the same exit-code contract as headless agents.
Concept 4 of 5

Pseudocode

# a structural rule matches SHAPE, not text
RULE no_swallowed_except:
  match: try {...} except { ONLY pass }

# the gate = run all three, fail on any non-zero
RUN ruff   check  file
RUN bandit -q     file        # B105, B110...
RUN ast-grep scan rules/ file

IF any exit_code != 0:
  gate = "FAIL"   # block + collect messages
ELSE:
  gate = "PASS"
Concept 4 of 5

Misconceptions

"grep for the dangerous string is enough."
Text search false-positives on strings/comments and misses aliased or re-spaced calls. As a security gate it's theater you'll regret.
"Linters need a cloud service."
ruff, bandit, and ast-grep are free and fully local — they run offline in milliseconds, perfect for a gate on every commit.

Match the parse tree, not the characters. An AST rule sees the real call node, immune to spacing, aliasing, and look-alikes.

ruff + bandit + ast-grep give a gate three deterministic judges, each exiting non-zero on a violation.

Concept 5 of 5

A failing gate is a teacher

A pre-commit gate runs checks just before a commit is recorded; if any fails, the commit is blocked — nothing enters history. The pre-commit framework manages them as layered hooks in one config file.

For an agent it's more than a net — it's a teacher. Feed the gate's specific rejection back to the model and it fixes exactly that and re-commits. And because a local hook can be skipped, the same checks re-run in CI on a clean server before merge.

Concept 5 of 5

The editor that won't let you hit Save

BEFORE: You save a document whenever you like; mistakes get caught later — if someone proofreads.

PAIN: When the writer is an agent producing dozens of drafts an hour, "caught later" means broken code already in history before anyone notices.

MAPPING: The gate is an editor that refuses Save until spell-check passes — and names the exact wrong word. The agent reads "B105, line 7," fixes it, saves clean. CI is the second editor on a shared server that can't be skipped.

Concept 5 of 5

Gate, correct, then CI

  1. Declare layered hooksA .pre-commit-config.yaml: hygiene, then ruff, then bandit, then ast-grep. pre-commit install wires them into Git.
  2. Commit triggers the gateHooks run on staged files; a non-zero exit blocks the commit.
  3. Feed the failure backSend the exact gate output and the file to the agent: "this was rejected, return the corrected file." Retry, bounded.
  4. CI re-runs on a clean serverLocal can be skipped with --no-verify; CI re-runs the identical checks and gates a protected branch before merge.
Concept 5 of 5

Pseudocode

FOR attempt IN 0..max_fixups:
  stage(file)
  passed, output = RUN pre-commit on file
  IF passed:
    commit(); RETURN 0          # lands clean
  IF attempt == max_fixups: BREAK
  # the gate teaches: send the real error back
  fixed = ASK Mistral(file, gate_output=output)
  write(file, fixed)
RETURN 2   # escalate, do not auto-merge

# CI: re-run `pre-commit --all-files` on a clean
# server; merge only if green (can't be skipped)
Concept 5 of 5

Misconceptions

"Let the agent retry the gate until it passes."
Unbounded retries on a gate the model can't satisfy spin forever. Cap the fixups and return a distinct exit code so an outer system escalates.
"If pre-commit passed locally, CI is redundant."
Local hooks can be skipped (--no-verify) or missing on a clone. CI re-runs on a clean server and gates a protected branch — the one commit that dodged local is the one CI catches.

The gate blocks the commit and hands back the reason; the agent fixes exactly that. Bound the retries: pass → commit, exhausted → escalate.

Pre-commit is the fast feedback; CI on a protected branch is the non-optional guarantee.

Test the core insight

One question per concept. Tap to reveal the answer.

Open the full module on desktop

The desktop version builds the whole stack against local Mistral — a workflow-file runner, a spec→generate→verify framework, an ast-grep rule, a .pre-commit-config.yaml, the correction loop, and a CI workflow — in runnable Python and Node, with a lab that wires a self-correcting commit gate end to end.

Open M23 on Desktop → Workflow files · frameworks · linting · gates · ~60 min

Module 23 of 26 · Open Source Track · Engineering Practices