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:
| State | Meaning | What may happen next |
|---|---|---|
queued | The review comment or issue was captured but not prepared. | Evidence work or task splitting |
blocked | A dependency, ambiguous manual fact, or failed prerequisite prevents work. | Human answer or dependency resolution |
ready | Scope, evidence, and acceptance criteria are present. | The loop may claim it |
investigating | The agent is gathering facts without editing driver behavior. | Return to ready, split, or block |
editing | One claimed task has permission to change only its allowed files. | Run checks |
checking | The current patch revision is being built and validated. | Sashiko only if checks pass |
reviewing | Sashiko is reviewing the exact checked patch range. | Resolve findings, hardware eligibility, or stop |
hardware eligible | Static checks and Sashiko are resolved for this exact revision. | Hardware only with explicit approval |
hardware testing | The optional DUT and host test plan is running. | Accept, block, or return to editing |
accepted | Required evidence and validation records are complete. | The task may satisfy dependants |
needs human | The 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.yamlis the durable work queue: upstream comments, failures, Sashiko findings, and deliberate follow-up tasks.state.jsonholds the current claimed task, current revision fingerprint, current phase, and whether hardware testing is approved.events.jsonlis 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.loopctlis 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:
-
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. -
A task with an open hardware question cannot be
readyfor 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:
- Its status is
ready. - Every task in
depends_onisaccepted. - Its allowed file scope is explicit.
- Its acceptance criteria are explicit.
- It has evidence supporting the intended change.
- Its
open_questionslist is empty for the part of the change being attempted. - No other task is currently claimed.
- 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:
| Command | Required behavior |
|---|---|
loopctl next | Select one ready task or print the blocker report. It changes no state. |
loopctl claim TASK | Locks state, verifies readiness again, writes the active task and a checkpoint receipt, then marks it investigating. |
loopctl begin-edit TASK | Requires evidence approval and allowed scope. Marks checks, Sashiko, and hardware stale. |
loopctl revision | Prints the current base, Git status, and revision fingerprint. |
loopctl record-check | Records command, tool versions, revision, exit code, timestamps, and log directory. |
loopctl begin-review | Refuses to run unless checks passed for the current revision. |
loopctl record-sashiko | Records the patch range, Sashiko version/settings identity, result location, findings, and revision. |
loopctl approve-hardware | Requires a named human decision and stores it in the event log. |
loopctl finish TASK | Refuses acceptance until all required records match the current revision. |
loopctl resume | Shows 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:
- Obtain an exclusive lock on
.agent-loop/state.json. - Recompute the revision fingerprint.
- Validate that the requested transition is legal.
- Write the new state to a temporary file.
- Validate the temporary file against your expected schema.
- Rename it over
state.json. - Append a timestamped event to
events.jsonl. - 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 type | Loop response |
|---|---|
| Reproducible change-related failure | Create or update the current repair task, then return to editing. |
| Known baseline failure | Record the baseline proof and ask for human policy before waiving it. Never silently ignore it. |
| Infrastructure failure | Retry within the configured limit using the same revision. If it persists, block. |
| Timeout | Preserve partial logs, mark the run interrupted or timed out, and stop for diagnosis. |
| Pass | Record 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 --versionoutput,- 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:
- Code fix — create a focused task, edit, and invalidate all validation.
- Rejected with proof — cite the exact code path, API contract, or manual fact that disproves it.
- Hardware question — stop; identify the manual section and the behavior that remains unknown.
- 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.enabledis 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:
- Run
loopctl resumeand inspect the state rather than relying on an old chat session. - Run
loopctl next. If it reports blockers, either investigate only what the blocker permits or request a focused human decision. - Claim exactly one ready task and save the checkpoint receipt.
- Read the task’s evidence and acceptance criteria. If evidence does not justify the intended edit, stop or return it to investigation.
- Make the smallest scoped edit that addresses the task.
- Recompute the revision and automatically mark prior validation stale.
- Run the configured static check wrapper and record its result.
- If checks pass, run the Sashiko wrapper on the exact current patch range and record its result.
- Resolve every Sashiko finding through a code fix, proof-based rejection, hardware question, or human decision.
- If hardware is explicitly enabled and approved, run the hardware wrapper. Otherwise record
not-requested; that is a valid outcome. - Recompute the revision one final time. Accept the task only if its required validation records match that revision.
- 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 condition | Required output |
|---|---|
| Hardware manual is unclear or contradictory | Exact question, manual location, affected register or behavior, and why no safe default exists |
| Review comments conflict | Linked comments, the conflict, and the decision needed |
| The task requires files outside scope | Proposed new scope and reason; no out-of-scope edit |
| Checks repeatedly fail | Logs, failure classification, revision, and whether baseline reproduces it |
| Sashiko finding cannot be proven or safely fixed | Finding ID, code locations reviewed, and the specific unresolved decision |
| Hardware is not approved | Record not-requested; do not open serial or test-host sessions |
| Agent reaches cycle limit | Summary 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 failure | Go to |
|---|---|
| V1 cannot be restored, baseline is unknown, or build evidence is missing | Module 2, Outcome 1 |
| Review comments are not captured, overlap, or conflict | Module 2, Outcomes 2–3 |
| Manual facts are unclear, incomplete, or contradictory | Module 3, Outcomes 1–3 |
| DSA callbacks, locking, phylink, MDIO, teardown, or managed resources are suspect | Module 4, Outcomes 1–2 |
| Binding YAML, examples, schema checks, or driver property handling are suspect | Module 4, Outcome 3 |
| Agent instructions or the unified check wrapper are incomplete | Module 5, Outcomes 1–2 |
| Sashiko setup or finding resolution needs work | Module 6, Outcomes 1–2 |
| You have approved a real DUT test | Module 7, Outcome 1 |
| The final series, cover letter, reviewer replies, or submission decision remains | Module 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