Skip to Content
TestingWalk-and-Fix Agent — close the loop from finding to patched app

Walk-and-Fix Agent — close the loop from finding to patched app

The walker surfaces real bugs (dead buttons, console errors, broken assets, failed requests). Walk-and-Fix turns those findings into FixIntent records, lets you (or an LLM) author a patch, writes the patched file into the deployed app on its tester VM, and restarts the systemd unit, no human redeploy required. Use it on a freshly walked app to clear the blocker/major findings in-place and re-walk to confirm.

Just want to see what regressed since your last walk? Skip the fix loop and run diff_walks over two walk IDs for a bucketed report (resolved, persisted, regressed, new). Use this page once you have the findings and want to actually patch them.

M1 caveat. This release ships the data plane end-to-end plus the local-vm apply path. The github-pr and provider-redeploy modes return 501 NotImplementedError. LLM-driven patch generation (auto-fill suggested_patch from the finding evidence) is the M2 deliverable; for now the caller supplies the patch body when invoking apply_fix.

How it works

  1. Walk an app and read its findings — the autonomous walker produces a walk with deduped Finding records (severity, category, selector, stack, network URL, parent_state_id trail).
  2. Propose a FixIntentpropose_fix(finding_id, mode='local-vm') wraps the finding in a FixIntent with status='proposed', target_file=null, suggested_patch=null. The FixIntent is the addressable unit the rest of the loop operates on.
  3. Decide on a patch — read the finding evidence, pick the file that needs to change, and produce its full new contents. M1 leaves this to the caller; M2 lands LLM patch generation via Context.sample().
  4. Apply the patchapply_fix(intent_id, patch, target_file, vm_name) writes the file under /opt/of-apps/<slug>/ via the guest agent (file_write with base64 payload) and restarts of-app-<slug>.service via the systemctl stopsystemctl reset-failedsystemctl restart triplet.
  5. Re-walk and diff — re-run walk_app and call diff_walks (feature 14) against the pre-fix walk. The targeted finding should land in the resolved bucket; if it persists, the FixIntent’s fix_outcome is persisted.
  6. Close the loopclose_finding(finding_id, resolution='fixed', fix_intent_id=...) marks the finding so future run_fix_cycle calls skip it.

Apply mechanism (local-vm mode)

apply_fix is a thin wrapper over two existing primitives. Knowing the wire shape helps when debugging a failed apply:

1. Locate the deployment root /opt/of-apps/<App.slug>/ 2. Write the patch via the guest agent guest_command( vm_name=<App.vm_name>, command="file_write", args={ "path": "/opt/of-apps/<slug>/<target_file>", "content": base64(patch), "encoding": "base64" } ) 3. Restart the systemd unit (triplet) systemctl stop of-app-<slug>.service systemctl reset-failed of-app-<slug>.service systemctl restart of-app-<slug>.service 4. Wait for the unit to be active and the health check to return 200, then mark FixIntent.status = 'applied' and stamp applied_at.

The target_file is always a path relative to the app’s deployment root (src/App.tsx, public/index.html, server/routes.js). The patch is the full file contents, not a unified diff; M1 keeps the apply path boring on purpose.

MCP tools

propose_fix

Create a FixIntent from a walker Finding. Captures severity/category/evidence so the patch step has context. M1 leaves target_file and suggested_patch as null (the caller fills them in on apply_fix).

propose_fix(finding_id='f-abc123def456', mode='local-vm', notes='broken image asset') → { "intent_id": "fi-a1b2c3d4e5f6", "finding_id": "f-abc123def456", "walk_id": "w-xyz789", "app_name": "my-shop", "severity": "minor", "category": "http_error", "status": "proposed", "target_file": null, "original_code": null, "suggested_patch": null, "mode": "local-vm", "session_token": "user-id-123", "next": "apply_fix(intent_id='fi-a1b2c3d4e5f6', patch=<full file>, target_file=<rel path>, vm_name=<from App>)" }

apply_fix

Write the patch into the deployed app and restart its systemd unit. local-vm is the only supported mode in M1; github-pr and provider-redeploy return 501 NotImplementedError.

apply_fix(intent_id='fi-a1b2c3d4e5f6', patch='<full index.html content>', target_file='public/index.html', vm_name='of-tester-001') → { "intent_id": "fi-a1b2c3d4e5f6", "mode": "local-vm", "success": true, "restart_status": "restarted", "applied_at": "2026-06-26T10:15:30Z", "session_token": "user-id-123", "next": "Fix applied. Re-walk the app to confirm: walk_app(app_name=..., app_url=..., vm_name='of-tester-001') then diff_walks(...) to confirm the finding cleared." }

get_fix_intent

Retrieve one FixIntent by id with its current status, target file, suggested patch, and apply result.

get_fix_intent(intent_id='fi-a1b2c3d4e5f6') → { "intent_id": "fi-a1b2c3d4e5f6", "finding_id": "f-abc123def456", "walk_id": "w-xyz789", "app_name": "my-shop", "severity": "minor", "category": "http_error", "status": "applied", "target_file": "public/index.html", "suggested_patch": "<full file content>", "apply_result": { "success": true, "restart_status": "restarted", "applied_at": "2026-06-26T10:15:30Z" }, "session_token": "user-id-123" }

list_fix_intents

List your FixIntents newest-first, optionally scoped to a single walk.

list_fix_intents(walk_id='w-xyz789') → { "intents": [{ "intent_id": "fi-a1b2c3d4e5f6", "finding_id": "f-abc123def456", "walk_id": "w-xyz789", "app_name": "my-shop", "severity": "minor", "category": "http_error", "status": "applied", "created_at": "2026-06-26T10:10:00Z", "updated_at": "2026-06-26T10:15:30Z" }], "session_token": "user-id-123" }

close_finding

Mark a walker Finding as resolved so future run_fix_cycle calls skip it. Resolutions: fixed, wont_fix, duplicate, not_a_bug.

close_finding(finding_id='f-abc123def456', resolution='fixed', notes='patched via apply_fix', fix_intent_id='fi-a1b2c3d4e5f6') → { "finding_id": "f-abc123def456", "walk_id": "w-xyz789", "resolution": "fixed", "notes": "patched via apply_fix", "fix_intent_id": "fi-a1b2c3d4e5f6", "closed_at": "2026-06-26T10:20:00Z", "session_token": "user-id-123" }

run_fix_cycle

M1 stub. Propose FixIntents for every blocker/major finding in a walk (or every NEW finding when baseline_walk_id is supplied), capped at max_findings. Returns the plan, does not execute the applies. Caller drives apply_fix per intent. M2 will auto-execute via Context.sample().

run_fix_cycle(walk_id='w-xyz789', baseline_walk_id='w-baseline', max_findings=5) → { "walk_id": "w-xyz789", "baseline_walk_id": "w-baseline", "candidate_findings": ["f-finding-1", "f-finding-2"], "proposed_intents": [ {"intent_id": "fi-111", "finding_id": "f-finding-1", "severity": "blocker", "...": "..."}, {"intent_id": "fi-222", "finding_id": "f-finding-2", "severity": "major", "...": "..."} ], "skipped": { "f-finding-3": "severity minor outside filter", "f-finding-4": "already resolved: fixed" }, "note": "M1 STUB: plan returned without execution. Use apply_fix(intent_id, patch, target_file) per intent. The LLM-driven auto-apply loop lands in M2 via FastMCP Context.sample().", "session_token": "user-id-123", "next": "M1 returned the plan. For each intent in proposed_intents[]: either apply_fix(...) or close_finding(...). M2 will auto-execute." }

REST surface

The MCP tools are thin wrappers over an authenticated REST API. All routes require user context (X-User-Id or X-User-Email); callers see only their own intents and findings. Cross-user access returns 403, unknown IDs return 404.

MethodPathPurpose
POST/api/tests/walk-fix/findings/{finding_id}/proposeCreate a FixIntent from a walker Finding
POST/api/tests/walk-fix/intents/{intent_id}/applyApply the patch and restart the unit (local-vm only; 501 for other modes)
GET/api/tests/walk-fix/intents/{intent_id}Fetch one FixIntent (status, target_file, suggested_patch, apply_result)
GET/api/tests/walk-fix/intentsList caller’s FixIntents newest-first (optional walk_id filter)
POST/api/tests/walk-fix/findings/{finding_id}/closeMark a Finding as resolved (fixed/wont_fix/duplicate/not_a_bug)
POST/api/tests/walk-fix/walks/{walk_id}/cycleRun fix_cycle: plan FixIntents for blocker/major (or NEW) findings, capped (M1 returns plan only)

Putting it together

  1. A walk deploys a broken app to a tester VM (App Deployment), discovers 3 findings (Autonomous Walker), stores them in the walk + findings JSON layout.
  2. Operator calls propose_fix(finding_id='f-abc123', mode='local-vm'). The MCP tool proxies to the libvirt backend, which scans walks to locate the finding and returns a FixIntent with status='proposed', target_file=null, suggested_patch=null.
  3. Operator reads the finding evidence (stack, selector, network URL) and decides the fix: a patched HTML/CSS/JS file. They call apply_fix(intent_id='fi-123', patch='<full file content>', target_file='src/App.tsx', vm_name='of-tester-001'). The backend writes /opt/of-apps/my-app/src/App.tsx via guest_command(file_write, base64), then runs systemctl stopreset-failedrestart on of-app-my-app.service.
  4. Once the unit is active and the health check returns 200, apply_fix returns success=true, restart_status='restarted'. The FixIntent transitions to status='applied'.
  5. Operator re-walks the app via walk_app(app_url=..., vm_name='of-tester-001') to produce a new walk; gets walk_id='w-new'.
  6. Operator calls run_fix_cycle(walk_id='w-baseline', baseline_walk_id='w-new') (or invokes diff_walks directly) to bucketize findings into resolved / persisted / regressed / new. If the targeted finding is gone, fix_outcome='resolved'; if it persists, fix_outcome='persisted'.
  7. Operator calls close_finding(finding_id='f-abc123', resolution='fixed', fix_intent_id='fi-123') so the next run_fix_cycle skips it.

Example prompts

For walk w-xyz789, propose a fix for finding f-abc123 (the broken image asset on /products). I'll supply the patched index.html.
List my open FixIntents for walk w-xyz789. For any that are still status='proposed', show me the finding evidence so I can decide on the patch.
Run a fix cycle over walk w-current with baseline w-yesterday, cap at 5 findings. I want the plan only; I'll drive apply_fix per intent.
  • Autonomous Walker — produces the Finding records (severity, category, selector, stack, network URL) that FixIntent wraps.
  • Walker Diffs and Tickets — bucketize findings across two walks (resolved / persisted / regressed / new) and serialize them into Linear / Jira / GitHub tickets. Use after apply_fix to confirm the finding cleared.
  • App Deployment — provides the App entity (slug, vm_name, port), the deployment root (/opt/of-apps/<slug>), and the systemd unit (of-app-<slug>.service) that apply_fix reuses.
  • Prompt-to-App Agent — shares the M1 synchronous-fallback pattern: real data plane, LLM generation deferred to M2.
  • MCP Integration — set up the OpenFactory MCP server.
Last updated on