How estimates work
What you see
- An outlook that answers whether to wait: “Almost done” (likely within 2 min), “A few minutes” (within 10), “Time for something else” (most likely within half an hour) or “Long task”. While confidence is low, the outlook is the headline and the likely range sits under it. At medium or high confidence the range leads and the outlook moves under it.
- A likely range, such as “About 8–14 min”: the middle half of the predicted remaining time, from the 25th to the 75th percentile. The tick on the bar marks the median, P50.
- Finishing up, when the run shows signs of wrapping up: its plan is complete, or its tests pass with no edits since, and the agent has started no tool for 20 seconds. It appears only once your own earlier runs show that those signs usually meant the end within two minutes; in some workflows passing tests are a midpoint. The remaining time then comes from what followed the same signs before. If the agent starts another tool, the estimate returns to normal.
- How long it could take, such as “Could take up to 30 min”, when the 90th percentile reaches well past the likely range. The bar shows the full band from P10 to P90, with the likely part drawn stronger.
- Confidence: low, medium or high. It describes the evidence behind the estimate, not just its width.
- A reason whenever the estimate moves noticeably, for example “Estimate increased after repeated test failures.”
The P10–P90 band targets 80% coverage. Once enough runs have finished, it is calibrated so that it does contain about 80% of outcomes on your machine, and Insights shows how often it actually did. Before that, it comes straight from the models and is labelled accordingly. The likely range is read from the same three percentiles on a log scale, each side with its own spread, so a long tail does not widen the range below the median.
What counts as one run
A run lasts from your prompt until the agent stops. A message you send while the agent is still working, or a notice that a background command it started has finished, continues the same run rather than starting a new one: it is one task to wait for. If the agent goes quiet without reporting an end and you prompt it again later, the earlier run is recorded as cancelled at its last activity, so the silence never counts as work. History recorded before this rule existed is repaired the same way when the models are trained.
The models
Four models run on your computer. Each joins the estimate once it has enough of your data to help, and Insights lists which ones are in use and why the others are not yet.
- History baseline. Total active time is treated as log-normal, a common shape for task durations: most runs are near a typical length and a few take much longer. It starts from deliberately wide built-in values for each kind of task and learns from your history, pooled across levels: all your runs, the same kind of task, the same agent, the same repository. Each level is pulled toward the one above in proportion to how little data it has, so three runs never outweigh three hundred. Once a run is under way, the remaining time is also read directly from your earlier runs that lasted at least as long, keeping their actual shape: when your history mixes quick answers with long tasks, the range covers both instead of predicting the gap between them. Effort level and model are learned as shared effects across all runs. Test failures, passing tests, plan progress and subagents then adjust it, each with a named reason.
- Phase simulation. A run moves between phases: planning, exploring, editing, testing, running commands, delegating. From your finished runs, runmarq learns how likely each next phase is (and how likely the run is to end) given the current phase and whether its last tool call failed, and how long each phase takes. It then simulates hundreds of possible continuations from where the run is now. Commands it has seen before, such as your test suite, use their own measured durations.
- Similar prompts. Each prompt is reduced to a salted, hashed word vector. The durations of earlier runs whose vectors are closest, and which lasted at least as long as the current run so far, give another estimate. You can turn this off in Settings.
- Learned model. After 100 finished runs, a gradient-boosted model is trained on moments from your runs: the task, effort, elapsed time, tool activity, test results, plan progress, token counts and working-tree changes, predicting the 10th, 50th and 90th percentiles of remaining time directly.
The models are combined by mixing their predictions rather than averaging their numbers, so when one expects a quick answer and another a long task, the range keeps both possibilities. How much each model counts is decided by testing them on your own recent runs as if they were live: models are fitted on older runs and asked to forecast newer ones at several moments, and each combination is scored on both its middle estimate and its range, every run counting once. Until there are many runs, the chosen weights stay close to the defaults, which favour the phase simulation because it starts from where the run actually is. A model that does not help gets no weight. When a run starts, its model is the one its session used last; the run's own model is known once its transcript has been read.
Cancelled and interrupted runs are treated as censored: they tell the models the work would have lasted at least as long as it did, not that it finished then. Failed runs count at half weight.
Calibration
Those same out-of-sample forecasts show how far outcomes fell outside the predicted range. runmarq widens or narrows each end of the range by the amount that would have kept 90% of outcomes above P10 and 90% below P90, separately for estimates made early and late in a run. This is split-conformal prediction: as long as new runs resemble your recent ones, the calibrated range covers at least 80% of outcomes. It starts after about 34 finished runs and is refreshed as more finish.
Signals beyond hook events
- Transcripts. Claude Code and Codex write session files on your computer. runmarq reads the numbers in them: how many model responses, output and thinking tokens, how full the context is, and compactions. Message text is not kept.
- Working tree. During a run,
git diff --numstatcounts files and lines changed since the run started. File names are not kept. - Commands. A shell command is identified by a salted hash of its program and subcommand (for example “npm test”), never its arguments or output, so its typical duration can be learned.
- Effort. The reasoning effort the agent reports, such as low or high.
Transcripts and working-tree changes can each be turned off in Settings → Estimation signals, as can the prompt comparison; command durations and effort are always used.
Why the estimate does not just count down
Subtracting elapsed time from a first guess goes wrong as soon as a run takes longer than typical. runmarq instead conditions on the run having survived this long. Early in a run, remaining time falls as time passes. When a run outlasts most similar runs, the estimate grows instead of reaching zero, and the reason “running longer than similar runs” appears.
Waiting for your approval is not active time. While an agent waits for you, the estimate pauses and the card says so.
Task classification
When a prompt arrives, a deterministic classifier inside the hook process assigns a category and a size from its wording: words like “typo”, “refactor” or “migrate”, the number of listed steps, file mentions, and words that widen or narrow scope. No model is called and the text is discarded afterwards. When a run starts without a prompt event, the estimate uses only history and is labelled as such; with no history either, runmarq shows “Not enough information yet” instead of a number.
Confidence
- Low: built-in values only, an unknown task, few similar runs, or a very wide range.
- Medium: at least eight comparable runs and a range that is not extremely wide.
- High: at least 25 comparable runs, a calibrated range that is reasonably narrow, and, once measured, interval coverage on your machine between 70% and 92%.
Measuring accuracy
After a run ends, its forecasts are compared with what happened. Insights reports median and mean error, how often the actual remaining time fell inside P10–P90, error at the start and after one, three and five minutes, error by agent and task type, and how much consecutive estimates move. A temporal holdout fits the models on your oldest runs and scores the initial estimate on the newest, with weights and calibration taken only from older runs, so it is not graded on data it has already seen.
None of these figures is shown until at least ten finished runs can be evaluated; a handful of runs cannot support an accuracy figure.
Progress versus remaining time
The number of tool calls says little about how close a run is to done, so runmarq never turns it into a percentage. When the agent keeps a real plan (Claude's task list, Codex's plan), the card shows completed and total steps, and the time per completed step sets a target for the rest that the final estimate leans toward, more with every step done.
The run's own pace matters too. Once it has made a few tool calls, the phase simulation compares its seconds per step with your usual and plays the rest of the run out at that speed, within limits, so a slow run is not forecast as if it were typical.
Checking it yourself
runmarq eval replays the runs recorded on your computer in the order they ended. It forecasts each one at several moments with models fitted only on the runs before it, as the live estimate would have been made, and reports how often the likely range held (ideally half the time), how often the full range held (ideally 80%), how often the outlook was right, the typical error, and how wide the ranges were. Add --json for the raw figures.
All of this is computed locally. Estimating a run makes no network requests and uses none of your agent's model usage.