Rewritten 2026-08-06 under the reframing in #195. Was "the agent loop is not reachable from the harness"; that is still true and is now the first half of a larger job.
First, and blocking
Nothing else in #195 can be built until this exists, and nothing built so far can be used without it.
$ grep -rn "minisweagent" src/agent_harness/*.py | grep -v adapters/
$
run chooses between SessionExecutor and Executor. The loop is neither. Every successful loop run to date went through a standalone script in a scratch directory, so the queue, gates, audit, attempt record, budgets and reviewer have never seen a loop-executed item.
What to build
One agentic role runner, not five bespoke paths. Given:
- a role (routed by
ModelClient, so chains, retry ladder, spend caps and recorded answers all apply unchanged),
- a task,
- an environment — the item's worktree, read-only or writable, screened by
CommandGuard,
- a termination condition and bounds,
it runs a bounded loop and returns a result. adapters/minisweagent.py already supplies the loop and both protocol implementations; this is the harness-side caller that does not exist.
Then use it for the implementer, and select it from run.
The seam
The loop replaces exactly one step and must not fork the pipeline:
claim -> worktree -> [ implement ] -> checks -> review -> commit -> PR
Direct mode's implement step is one call returning edit blocks, rendered to a diff, applied. The loop's is many turns in the worktree, leaving changes in the tree. Everything either side is identical.
So the loop's changes should arrive as git diff against the worktree's base and enter the existing _from_diff, keeping the patch record, checks, reviewer, commit and attempt log exactly as they are. A second copy of that sequence is a second place for the gates to drift, which _from_diff was factored to prevent.
Note #216: the worktree must be synced to the item's base before anything reads it. That bug — the diff computed against the previous item's branch — is fixed for direct mode, and the loop must not reintroduce it.
Two decisions I will not guess at
1. Who runs the checks? The loop is told the check command and will run it as feedback; the harness then runs it as the gate. Running both is honest and pays twice. Running only the harness's removes the feedback that makes a loop work. My view is both, because feedback and a gate are not the same thing — but it is a real cost decision and should be recorded.
2. How does a per-item budget bound a loop? budgets.py bounds an item's wall-clock and spend; step_limit bounds turns. #217 made the adapter able to map them, and was explicit that this is enforceable, not yet enforced, because nothing calls build(). The executor checks budgets at boundaries and a loop's boundary is the whole loop, so a long loop can exceed an item budget without anything noticing.
Acceptance
agent-harness run can select the loop, and an item is delivered through it with the queue, gates, audit and reviewer all involved.
- The event stream for a loop-executed item is legible: which turns ran, what was refused, what it cost. ~30 calls attributed to one item, and
pricing correct across them.
- A per-item budget stops a loop that overruns it.
- End-to-end tests in the mock pattern — scripted model, real git repository, real shell — covering delivery, refusal, budget exhaustion and a loop that never terminates.
Blind spots
Rewritten 2026-08-06 under the reframing in #195. Was "the agent loop is not reachable from the harness"; that is still true and is now the first half of a larger job.
First, and blocking
Nothing else in #195 can be built until this exists, and nothing built so far can be used without it.
runchooses betweenSessionExecutorandExecutor. The loop is neither. Every successful loop run to date went through a standalone script in a scratch directory, so the queue, gates, audit, attempt record, budgets and reviewer have never seen a loop-executed item.What to build
One agentic role runner, not five bespoke paths. Given:
ModelClient, so chains, retry ladder, spend caps and recorded answers all apply unchanged),CommandGuard,it runs a bounded loop and returns a result.
adapters/minisweagent.pyalready supplies the loop and both protocol implementations; this is the harness-side caller that does not exist.Then use it for the implementer, and select it from
run.The seam
The loop replaces exactly one step and must not fork the pipeline:
Direct mode's implement step is one call returning edit blocks, rendered to a diff, applied. The loop's is many turns in the worktree, leaving changes in the tree. Everything either side is identical.
So the loop's changes should arrive as
git diffagainst the worktree's base and enter the existing_from_diff, keeping the patch record, checks, reviewer, commit and attempt log exactly as they are. A second copy of that sequence is a second place for the gates to drift, which_from_diffwas factored to prevent.Note #216: the worktree must be synced to the item's base before anything reads it. That bug — the diff computed against the previous item's branch — is fixed for direct mode, and the loop must not reintroduce it.
Two decisions I will not guess at
1. Who runs the checks? The loop is told the check command and will run it as feedback; the harness then runs it as the gate. Running both is honest and pays twice. Running only the harness's removes the feedback that makes a loop work. My view is both, because feedback and a gate are not the same thing — but it is a real cost decision and should be recorded.
2. How does a per-item budget bound a loop?
budgets.pybounds an item's wall-clock and spend;step_limitbounds turns. #217 made the adapter able to map them, and was explicit that this is enforceable, not yet enforced, because nothing callsbuild(). The executor checks budgets at boundaries and a loop's boundary is the whole loop, so a long loop can exceed an item budget without anything noticing.Acceptance
agent-harness runcan select the loop, and an item is delivered through it with the queue, gates, audit and reviewer all involved.pricingcorrect across them.Blind spots
Executorcan take an alternative implement step without disturbing A formatter in check mode is a gate no model reliably passes, and the declared fix is not allowed to clear it #155's fix-then-recheck or The context budget is a target, not a ceiling: every item pays for the whole budget whatever it needs #152's context selection is unchecked; both touch that path.pricinghas never attributed a multi-turn role to one item. It may be correct and has not been looked at.