Build an Agent Feedback Loop in Shipfox
Route two objective checks to independent recovery steps until both checks pass or an attempt bound is reached.
This guide builds one job with test and lint feedback loops. Each failed check routes to its own recovery step by the checking step's key.
Before you begin
You need:
- A repository with
npm testandnpm run lintcommands. - Configured agent defaults for the project.
- A safe branch where an agent can edit test and lint failures.
- A Shipfox validator and runtime that support
step.restart.from.key.
Add the workflow
Create .shipfox/workflows/fix-tests.yml. This is a complete workflow:
# yaml-language-server: $schema=https://www.shipfox.io/docs/workflow.schema.json
name: Fix failing tests
runner: shipfox
triggers:
manual:
source: manual
jobs:
improve:
execution_timeout: "30m"
steps:
- key: implement
prompt: >
Fix the failing tests and lint errors in this repository.
Run both checks before finishing.
- key: test_failure_handler
if: '${{ has(step.restart) && has(step.restart.from.key) && step.restart.from.key == "test" }}'
prompt: >
Fix the test failure. Reproduce it and correct its cause.
Check feedback: ${{ step.restart.feedback }}
- key: test
run: npm test
gate:
success: step.exit_code == 0
on_failure:
restart_from: test_failure_handler
feedback: The tests still fail. Run them again and fix the cause.
- key: lint_failure_handler
if: '${{ has(step.restart) && has(step.restart.from.key) && step.restart.from.key == "lint" }}'
prompt: >
Fix the lint failure without weakening the rules.
Check feedback: ${{ step.restart.feedback }}
- key: lint
run: npm run lint
gate:
success: step.exit_code == 0
on_failure:
restart_from: lint_failure_handler
feedback: Lint still fails. Run it again and fix the reported issues.Replace the two check commands with commands that prove the required results. Keep an authored key on each checking step. The keys provide source identity for their recovery predicates.
Run and inspect it
- Commit and push the workflow to the project's default branch.
- Wait for its definition to sync.
- Open the workflow and select Run.
- Open the
improvejob in the run detail. - Confirm both recovery steps skip before their matching check fails.
- If
testfails, confirm onlytest_failure_handlerruns. - After
testpasses, confirmlint_failure_handlerstays skipped. - If
lintfails, confirm onlylint_failure_handlerruns. - Confirm the job finishes after both checks pass.
The test cause stays visible after test passes. The lint handler still skips
because its predicate compares the source key. step.is_retry cannot make this
distinction.
Exact gate fields and validation rules live in Gate fields. The Contexts reference owns the exact restart field shape and availability.
Migrate from feedback-prefix routing
Confirm the deployed validator and runtime support step.restart.from.key
before adopting the new predicates. A merged change or green CI is not runtime
deployment evidence.
For each existing route:
- Add an authored
keyto the checking step that owns the gate. - Replace the handler's feedback-prefix test with the guarded source-key test.
- Keep useful diagnostic text in
gate.on_failure.feedback. - Remove the machine-readable prefix after no consumer parses it.
For example, replace a verification_retry: prefix check with this predicate
on the existing verification recovery step:
has(step.restart) && has(step.restart.from.key) && step.restart.from.key == "verify"The verify key belongs on the failing check. It does not belong on the
restart_from target.
The job timeout is an outer wall-clock bound. The gate also has its own attempt limit. See Limits. For the evaluation model, see Feedback loops.