Skip to content
docsv0.42.1

ProgressProbe

The caller's opinion on whether the agent's run is still making SEMANTIC progress (greenhouse decisions/0185). The gateway drives the loop but does not own the session stream, so it cannot measure progress itself — the same split {@see PlanBoard} already made for the plan: the loop asks, the caller derives from the stream it owns. The fifth live run showed why tokens are the wrong unit: 811k of them over 42 calls for 8 artifacts, thousands per call spent reasoning about how to write a test instead of writing it. Whoever implements this measures growth in evidence, materializations and closed todos — never in words. A probe is an OBSERVER with one lever: when it answers stalled, the orchestrator puts the notice (the caller words the forced choice) in front of the model. Legacy probes hold one answer to that choice. The optional recovery observation retains it through preparation until measured growth or exhaustion. The probe never rewrites history; the loop enforces its reported result.

ProgressProbe::afterStep()

abstract public function afterStep(int $step): ?array

Consulted after each completed step of the agent loop. `null` means no opinion. It never clears an explicitly pending recovery. Probes omitting `recovery` retain the legacy one-answer notice behavior. An array carries: - `stalled` (bool): whether the window since the last checkpoint shows no semantic growth; - `notice` (string): the ONE system line to put in front of the model — the caller words the choice (execute an operation, `HOUSE_DEBT:`, a human decision, `ABANDON:`) and the receipt numbers backing it; - `receipt` (array): the derivation's telemetry projection, carried into the stalled sentinel so the surface can show WHY the leg ended. The optional `recovery` field opts into bounded semantic recovery (0343/0660): `pending` retains the forced choice across preparation steps; `recovered` with stalled:false clears it only after measured growth; `exhausted` ends the leg before another model call. Pending/exhausted carry stalled:true and a nonempty notice. The producer owns the window and its budget; the loop never infers progress from tool names, success, or receipt fields. An unavailable observer cannot invent recovery or expiry; the total step budget still holds. The optional `complete` (bool) says the producer's RECORDED work is complete — every tracked item closed with evidence (greenhouse decisions/0476). The notice then asks for the final answer, and a plain answer after it IS a final answer, not a stall: finishing is a way out, while prose instead of the work stays without one wherever the work is not complete. The optional `epilogue` (int) opens the EPILOGUE (greenhouse decisions/0477): the producer verified that the work phase closed, and this is how many model calls remain to write the output. A nonempty notice announces it once and a plain answer after it is final; `0` ends the leg before another model call with its own cause, `epilogue_exhausted`. The producer owns the count and its reopening — the loop never infers closure. Terminating a task is the agent's judgement; verifying it is the house's; how much budget the output deserves after closure is the house's policy.

Parameters

Parameters of afterStep()
NameTypeDescription
$stepintthe zero-based loop step that just completed