Create your own
Lesson illustration

Designing a Resumable Task Loop with Gated Validation and Hardware Testing

Welcome. This course treats your existing V1 DSA driver and its upstream feedback as the real project, not as a toy example. The aim is to make an AI agent useful over many repair cycles without allowing it to invent hardware behavior, lose review context, or declare success based on a single passing build.

This lesson builds the durable control loop around the agent. It is tool-neutral: Cursor and Claude Code can both operate inside it because the important interface is a set of repository files, commands, evidence records, and gates—not a vendor-specific feature. By the end, you will have a loop that selects one justified task, records its state, validates a specific code revision, runs Sashiko, optionally allows hardware testing, and becomes safely resumable after any interruption.

Plan for about 40–45 minutes: roughly 7 minutes of video, 12 minutes of targeted reading, and the rest spent creating the loop files in your kernel work area.


Treat the agent loop as a guarded state machine

An agent is not the loop. The harness is the loop: the durable state, available commands, evidence rules, checkpoints, and exit conditions that constrain an agent’s work.

A normal coding-agent session is often too linear: prompt, edit, test, answer. That is not enough for upstream DSA work. A driver repair may involve a review comment, a Device Tree binding, DSA lifetime rules, an unclear register description, static checks, an AI review finding, and perhaps hardware observations. A useful loop must preserve the distinction between all of those.

Watch this short explanation of why code-changing work needs a closed loop rather than a one-shot prompt.

Harness Engineering Explained: Inside the Stack Behind Antigravity, Claude Code & Cursor

In “Harness Engineering Explained,” Google Cloud Tech distinguishes a predictable linear harness from a code-editing feedback loop, then shows why safety controls belong in the harness rather than in a hopeful prompt.

Watch the closed loop for the model of edit, check, inspect the failure, and repeat only within a bounded number of attempts. Then watch the guardrails for the principle that destructive or externally visible actions should be blocked by the environment, not merely discouraged in prose.

For this project, think of each task as moving through controlled states:

StateMeaningWhat may happen next
queuedThe review comment or issue was captured but not prepared.Evidence work or task splitting
blockedA dependency, ambiguous manual fact, or failed prerequisite prevents work.Human answer or dependency resolution
readyScope, evidence, and acceptance criteria are present.The loop may claim it
investigatingThe agent is gathering facts without editing driver behavior.Return to ready, split, or block
editingOne claimed task has permission to change only its allowed files.Run checks
checkingThe current patch revision is being built and validated.Sashiko only if checks pass
reviewingSashiko is reviewing the exact checked patch range.Resolve findings, hardware eligibility, or stop
hardware eligibleStatic checks and Sashiko are resolved for this exact revision.Hardware only with explicit approval
hardware testingThe optional DUT and host test plan is running.Accept, block, or return to editing
acceptedRequired evidence and validation records are complete.The task may satisfy dependants
needs humanThe agent has reached a decision it must not make.Wait for a recorded human decision

The key property is revision binding. A passed build is not a general property of “the driver”; it is evidence about one exact patch state.

Define a revision fingerprint as:

Every check, Sashiko result, and hardware result records that fingerprint. When a code or binding edit changes the fingerprint, all earlier validation becomes stale. The loop must then send the new revision through checks and Sashiko again. This is how you prevent a common agent failure: fixing a review finding after tests passed, then accidentally treating old test results as evidence for new code.


Keep workflow memory outside the agent’s context window

An agent may lose context, be interrupted, or be replaced by a different model. Therefore, its conversational memory must never be the authoritative record. Store operational state in local files beside the kernel tree, normally ignored by Git so logs and temporary state do not enter an upstream patch.

A practical minimal layout is:

kernel-worktree/
├── .agent-loop/
│   ├── config.yaml
│   ├── tasks.yaml
│   ├── state.json
│   ├── events.jsonl
│   ├── checkpoints/
│   └── runs/
├── docs/
│   ├── review-worklist.md
│   ├── evidence/
│   └── agent-contract.md
└── tools/
    ├── loopctl
    ├── check-driver.sh
    ├── run-sashiko.sh
    └── run-hardware.sh

The files have distinct jobs:

  • tasks.yaml is the durable work queue: upstream comments, failures, Sashiko findings, and deliberate follow-up tasks.
  • state.json holds the current claimed task, current revision fingerprint, current phase, and whether hardware testing is approved.
  • events.jsonl is append-only. It records commands, start and finish times, exit codes, output locations, state changes, and human decisions.
  • checkpoints/ holds a short text receipt before edits: base commit, clean or dirty status, task ID, and the intended scope.
  • runs/ holds timestamped check logs, Sashiko output, and eventually hardware logs.
  • loopctl is the small controller that reads and updates state atomically. It does not need to be intelligent.
  • The three runner scripts provide stable interfaces even when the internal build command, Sashiko version, or lab procedure changes.

Use this configuration as a starting point. Fill in the kernel version, worktree path, allowed paths, and actual command details for your project.

# .agent-loop/config.yaml
project:
  kernel_version: "REPLACE_WITH_TARGET_KERNEL"
  base_ref: "REPLACE_WITH_SUBMISSION_BASE"
  worktree: "/absolute/path/to/kernel-worktree"

scope:
  allowed_paths:
    - "drivers/net/dsa/your_driver/"
    - "Documentation/devicetree/bindings/net/dsa/"
    - "MAINTAINERS"

commands:
  checks: "./tools/check-driver.sh"
  sashiko: "./tools/run-sashiko.sh"
  hardware: "./tools/run-hardware.sh"

policy:
  default_hardware_enabled: false
  require_clean_start: true
  require_evidence_before_edit: true
  require_sashiko_after_code_change: true
  maximum_edit_cycles_per_task: 3
  maximum_same_command_retries: 1

human_gates:
  unclear_hardware_manual: required
  conflicting_hardware_evidence: required
  scope_expansion: required
  architecture_decision: required
  external_lab_action: required
  destructive_git_action: required

The exact task schema matters more than the exact file format. It should force the agent to make its claims inspectable. Here is a suitable task record for an upstream review comment:

- id: REV-017
  title: "Clarify and correct port handling in driver setup"
  source:
    type: "upstream-review"
    thread_url: "REPLACE_WITH_THREAD_URL"
    patch_version: "v1"
    reviewer: "REPLACE_WITH_NAME"
    original_comment: "REPLACE_WITH_EXACT_COMMENT"

  status: ready
  priority: 30
  depends_on: []

  scope:
    allowed_paths:
      - "drivers/net/dsa/your_driver/your_driver.c"
    non_goals:
      - "Do not alter register programming without manual evidence."

  evidence:
    conclusion: "supports change"
    facts:
      - source: "manual section and page"
        statement: "REPLACE_WITH_VERIFIED_FACT"
        confidence: clear
      - source: "target-kernel DSA API"
        statement: "REPLACE_WITH_VERIFIED_API_RULE"
        confidence: clear
    assumptions: []
    open_questions: []

  acceptance:
    - "Reviewer concern is addressed in code or rejected with cited proof."
    - "The error path and return value are reviewed."
    - "Configured build and DT checks pass."
    - "Sashiko has no unresolved finding for this revision."

  verification:
    revision: null
    checks: stale
    sashiko: stale
    hardware: not-requested

  history: []

Two design rules make this useful:

  1. Facts, patterns, and guesses are separate fields.
    An analogous driver can support a design pattern; it cannot prove your switch’s register behavior. A current V1 behavior cannot prove correctness either.

  2. A task with an open hardware question cannot be ready for a hardware-dependent edit.
    The agent may continue with unrelated tasks, but it must not quietly choose a register value, reset sequence, or port mode because the manual is vague.

The Sashiko contributor guidance expresses the same general workflow principle: give each stage one clear responsibility, provide only enough context to solve that stage, and make retries safe rather than duplicating side effects.

sashiko/GEMINI.md at main · sashiko-dev/sashiko

Read this Sashiko contributor guidance as a design reference for your own driver-repair harness. Its Rust-specific details are not the point here; focus on stage boundaries, minimal context, early exits, and idempotent retries.

In “LLM Workflow Design,” first read subsection “1. Stage Design & Data Flow,” beginning with stage responsibility. Notice the test for sufficient evidence: if a human cannot confidently decide from the supplied context, neither can the agent. Then read subsection “3. Resilient & Idiomatic Rust,” from safe retries. Apply that principle to tool failures and malformed agent output: retry a read-only stage safely, but never repeat an external lab action blindly.


Define readiness before asking the agent to edit

“Choose the next task” must be deterministic. Otherwise an agent tends to pick an interesting-looking issue, combine several reviewer comments, or start a broad cleanup that cannot be defended upstream.

Your loopctl next command should select only a task that meets all of these conditions:

  1. Its status is ready.
  2. Every task in depends_on is accepted.
  3. Its allowed file scope is explicit.
  4. Its acceptance criteria are explicit.
  5. It has evidence supporting the intended change.
  6. Its open_questions list is empty for the part of the change being attempted.
  7. No other task is currently claimed.
  8. The worktree is at the configured base or at the recorded series state, according to your project policy.

When several tasks are ready, sort by a stable rule, such as numeric priority followed by task ID. Do not ask the model to “pick the most important one.” The human has already expressed priority through the work list.

If nothing is ready, loopctl next should print a blocker report, not an invitation to improvise:

NO READY TASK

MANUAL-004:
  blocked by: unclear reset-bit behavior in manual section 8.4
  required decision: does reset preserve MDIO configuration?

REV-023:
  blocked by: depends on DT-006
  required action: finish binding validation first

SASHIKO-011:
  blocked by: requires concrete code citation to reject finding
  required action: investigate execution path

That output is valuable. It tells the next agent exactly what is missing and gives you a short human decision queue.

The agent contract

Create docs/agent-contract.md and ensure both Cursor and Claude Code receive it at the beginning of a task. Keep it short enough to be read every time.

You are operating a Linux DSA driver repair loop.

1. Read .agent-loop/config.yaml, the claimed task, and its evidence.
2. Work on one claimed task only. Do not combine cleanups.
3. Treat the hardware manual, target-kernel source, and recorded review thread
   as evidence. Treat V1 behavior and analogous drivers as non-proof patterns.
4. If evidence is unclear, incomplete, or conflicting, stop in needs-human.
   State the exact question, affected code, and evidence references.
5. Edit only files allowed by the task. Report any needed scope expansion.
6. After every source or binding edit, mark checks, Sashiko, and hardware
   results stale for the new revision.
7. Run only the configured wrappers. Save outputs under .agent-loop/runs.
8. Do not start hardware testing unless state records explicit human approval
   and the current revision has passing checks and resolved Sashiko findings.
9. Do not discard work, rewrite Git history, push, email, or touch lab systems
   unless the recorded policy and a human approval permit it.
10. End with a state update and a concise evidence-based report. Never claim
    that an issue is fixed merely because a command completed.

This contract is intentionally stricter than a general “be careful” instruction. It gives the agent concrete exit states. A model that is forced to choose between “fix” and “not a bug” may invent confidence; needs-human, blocked, and “insufficient evidence” are essential escape hatches.


Implement the control points, not a giant prompt

Your loop controller can be a small shell, Python, or Go utility. Its implementation language is unimportant. Its behavior is not.

At minimum, implement these commands:

CommandRequired behavior
loopctl nextSelect one ready task or print the blocker report. It changes no state.
loopctl claim TASKLocks state, verifies readiness again, writes the active task and a checkpoint receipt, then marks it investigating.
loopctl begin-edit TASKRequires evidence approval and allowed scope. Marks checks, Sashiko, and hardware stale.
loopctl revisionPrints the current base, Git status, and revision fingerprint.
loopctl record-checkRecords command, tool versions, revision, exit code, timestamps, and log directory.
loopctl begin-reviewRefuses to run unless checks passed for the current revision.
loopctl record-sashikoRecords the patch range, Sashiko version/settings identity, result location, findings, and revision.
loopctl approve-hardwareRequires a named human decision and stores it in the event log.
loopctl finish TASKRefuses acceptance until all required records match the current revision.
loopctl resumeShows the active task, current revision, stale validations, latest failed command, and the next legal action.

For every state write, use a lock and an atomic update:

  1. Obtain an exclusive lock on .agent-loop/state.json.
  2. Recompute the revision fingerprint.
  3. Validate that the requested transition is legal.
  4. Write the new state to a temporary file.
  5. Validate the temporary file against your expected schema.
  6. Rename it over state.json.
  7. Append a timestamped event to events.jsonl.
  8. Release the lock.

The controller should not attempt to recover by itself from uncertainty. If the process is interrupted during a build, the recorded run becomes interrupted; resume may rerun the same read-only check. If the process is interrupted during a DUT test, it should stop and request a human check of lab state before reusing the test environment.

The revision invalidation rule

This is the central mechanism. Run it at the beginning and end of every loop stage.

If the current revision differs from the revision recorded by checks:
    mark checks stale
    mark Sashiko stale
    mark hardware stale

If checks are stale:
    do not start Sashiko

If Sashiko is stale:
    do not accept the task
    do not start hardware testing

If hardware is stale:
    do not use an old runtime result as evidence for the current code

A conservative revision fingerprint can include both committed and uncommitted work:

{
    printf '%s\n' "base=$(git rev-parse "$BASE_REF")"
    git diff --binary "$BASE_REF"...HEAD
    git diff --binary HEAD
} | sha256sum

Record the base reference and its resolved commit ID beside the hash. The point is not cryptographic sophistication; the point is to make it impossible to confuse results for different patches.

The controller should also compare the current diff against the task’s allowed_paths. If the agent touched another driver, a generic cleanup file, or an unrelated binding, stop in needs-human or split the work. A small, coherent patch is easier to validate, explain in a reviewer reply, and send through Sashiko.


Run the static-validation and Sashiko gates

Your check wrapper from the earlier workflow setup should be the only command the agent calls for static validation:

./tools/check-driver.sh --out ".agent-loop/runs/RUN_ID/checks"

Internally, it can run your selected kernel configuration build, warning checks, kernel style or static checks, YAML binding validation, and DTB checks. The wrapper must save:

  • the exact commands,
  • the kernel base and compiler versions,
  • configuration identity,
  • stdout and stderr,
  • exit codes,
  • start and finish times,
  • a short machine-readable summary.

A nonzero exit code does not automatically mean the driver change is wrong. It could be a baseline failure, host setup problem, timeout, or a real regression. The loop must classify it before proceeding:

Result typeLoop response
Reproducible change-related failureCreate or update the current repair task, then return to editing.
Known baseline failureRecord the baseline proof and ask for human policy before waiving it. Never silently ignore it.
Infrastructure failureRetry within the configured limit using the same revision. If it persists, block.
TimeoutPreserve partial logs, mark the run interrupted or timed out, and stop for diagnosis.
PassRecord the current revision and permit Sashiko.

Sashiko belongs after the basic checks, not before. It is a second review gate, not a substitute for compilation, DT schema checks, or human hardware judgment.

Read the Sashiko documentation now, focusing on its local-review behavior and review stages.

GitHub - sashiko-dev/sashiko: Agentic review of Linux Kernel code changes · GitHub

Read the Sashiko README to understand what the review gate can inspect, why its output is not proof by itself, and why local review is appropriate for an in-progress patch series.

In “Prompts,” read the review stages. Pay particular attention to execution-flow, resources, locking, and hardware, followed by consolidation and verification; these are especially relevant to DSA setup, teardown, MDIO, phylink, and register-access changes. Then, in “Usage,” read subsection “1. Local Review (Recommended)” from local review behavior. Use local review for this loop so review does not mutate the kernel checkout or send anything externally.

Do not hard-code a possibly outdated Sashiko CLI invocation into the general loop. Instead, make tools/run-sashiko.sh a project-specific adapter. Its stable interface can be:

./tools/run-sashiko.sh \
    --base "$BASE_REF" \
    --head HEAD \
    --out ".agent-loop/runs/RUN_ID/sashiko"

The adapter is where you put the command verified against your installed Sashiko version and its chosen settings file. It should record:

  • sashiko --version output,
  • the settings file path and a checksum of it,
  • the reviewed Git range,
  • the current revision fingerprint,
  • complete review output,
  • a normalized findings file.

Sashiko can ingest local Git patches and uses separate analysis and consolidation stages, but its output remains probabilistic. Treat every finding as an item requiring a resolution, not as an automatic code change.

Each finding must end in one of four states:

  1. Code fix — create a focused task, edit, and invalidate all validation.
  2. Rejected with proof — cite the exact code path, API contract, or manual fact that disproves it.
  3. Hardware question — stop; identify the manual section and the behavior that remains unknown.
  4. Human decision — use when the issue is architectural, policy-based, or cannot be safely resolved from available evidence.

“No findings” is a valid early exit for Sashiko. Do not ask an agent to manufacture extra concerns merely because a review stage ran.


Make hardware testing an explicit branch, never the default

Hardware testing is valuable, but the lab is an external system with state: serial sessions, DUT power state, attached hosts, VLAN configuration, cable topology, and possibly other people’s work. Therefore it is optional and closed by default.

The loop may enter hardware testing only when all of the following are true:

  • hardware.enabled is true in the durable state;
  • a human approval event names the approver, test purpose, and task;
  • the configured static checks passed for the current revision;
  • Sashiko is complete and every finding is resolved for the current revision;
  • the test plan states expected observations and recovery boundaries;
  • the DUT and test hosts have been identified and their logs have a new run directory.

This lesson does not yet configure tmux, serial access, or multi-host traffic testing in detail. The loop only needs a stable hardware-runner interface:

./tools/run-hardware.sh \
    --task "$TASK_ID" \
    --revision "$REVISION" \
    --out ".agent-loop/runs/RUN_ID/hardware"

The runner must never treat a runtime observation as a hardware fact without qualification. For example:

  • “Port 2 did not forward traffic after reset” is an observation.
  • “The reset bit clears VLAN state” is a hardware claim and requires manual evidence or a focused human answer.
  • “The driver must call this helper before that helper” is a kernel API claim and requires target-kernel source evidence.

If a hardware test leads to a code or binding change, the loop returns to editing. The resulting revision has no valid checks, no valid Sashiko report, and no valid hardware result until it completes those gates again. This may feel repetitive, but it prevents runtime-driven fixes from bypassing the same review discipline as review-comment-driven fixes.


A complete run, including stop and resume behavior

For each task, the live loop is deliberately small:

  1. Run loopctl resume and inspect the state rather than relying on an old chat session.
  2. Run loopctl next. If it reports blockers, either investigate only what the blocker permits or request a focused human decision.
  3. Claim exactly one ready task and save the checkpoint receipt.
  4. Read the task’s evidence and acceptance criteria. If evidence does not justify the intended edit, stop or return it to investigation.
  5. Make the smallest scoped edit that addresses the task.
  6. Recompute the revision and automatically mark prior validation stale.
  7. Run the configured static check wrapper and record its result.
  8. If checks pass, run the Sashiko wrapper on the exact current patch range and record its result.
  9. Resolve every Sashiko finding through a code fix, proof-based rejection, hardware question, or human decision.
  10. If hardware is explicitly enabled and approved, run the hardware wrapper. Otherwise record not-requested; that is a valid outcome.
  11. Recompute the revision one final time. Accept the task only if its required validation records match that revision.
  12. Commit or prepare the self-contained change according to your project’s patch policy, then clear the active task.

The normal stopping conditions are as important as the success condition:

Stop conditionRequired output
Hardware manual is unclear or contradictoryExact question, manual location, affected register or behavior, and why no safe default exists
Review comments conflictLinked comments, the conflict, and the decision needed
The task requires files outside scopeProposed new scope and reason; no out-of-scope edit
Checks repeatedly failLogs, failure classification, revision, and whether baseline reproduces it
Sashiko finding cannot be proven or safely fixedFinding ID, code locations reviewed, and the specific unresolved decision
Hardware is not approvedRecord not-requested; do not open serial or test-host sessions
Agent reaches cycle limitSummary of attempts, diffs, evidence gained, and the next human decision

This structure gives you an important operational benefit: stopping does not mean losing progress. An interrupted agent can be replaced immediately. The replacement runs loopctl resume, reads one task and its evidence, sees which validations are stale, and continues from a known legal state.


Use the course as a just-in-time map

When the loop stops, do not broaden the prompt randomly. Go to the lesson matching the failure type:

Loop stage or failureGo to
V1 cannot be restored, baseline is unknown, or build evidence is missingModule 2, Outcome 1
Review comments are not captured, overlap, or conflictModule 2, Outcomes 2–3
Manual facts are unclear, incomplete, or contradictoryModule 3, Outcomes 1–3
DSA callbacks, locking, phylink, MDIO, teardown, or managed resources are suspectModule 4, Outcomes 1–2
Binding YAML, examples, schema checks, or driver property handling are suspectModule 4, Outcome 3
Agent instructions or the unified check wrapper are incompleteModule 5, Outcomes 1–2
Sashiko setup or finding resolution needs workModule 6, Outcomes 1–2
You have approved a real DUT testModule 7, Outcome 1
The final series, cover letter, reviewer replies, or submission decision remainsModule 8, Outcomes 1–2

Key takeaways

A robust driver-development loop is not a large autonomous prompt. It is a small state machine backed by durable files and strict gates.

  • The task record—not agent memory—holds the review context, evidence, scope, and acceptance criteria.
  • Every validation result is tied to an exact revision fingerprint.
  • Every later code or binding edit invalidates checks, Sashiko, and hardware evidence for the old revision.
  • Sashiko runs only after static checks and produces tasks or proof obligations, not automatic truth.
  • Hardware testing is an explicit, human-approved branch after static and review gates.
  • Clear stop states are a feature: they prevent guesses about hardware and make the loop resumable.

Next, strengthen the operational safety around this loop: isolated Git work areas, an allowed-command policy, time and retry limits, checkpoint recovery, and required human approval before dangerous repository or lab actions.

Can't find a good explanation? Sign up and we'll make it for you

Sign up