> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Bounding a long run

> A run is bounded by the money and time you set, not by an arbitrary step count. Set nothing and a run keeps going until the work is done (or the no-progress guard stops it, below).


A run is bounded by the **money and time you set**, not by an arbitrary step count. Set nothing
and a run keeps going until the work is done (or the no-progress guard stops it, below).

## The bounds

| Bound | Environment variable | `oceancode run` flag | Slash command (this session) |
|---|---|---|---|
| Spend cap (USD) | `OC_MAX_USD` | `--max-usd 25` | `/budget 25` |
| No spend cap | — | `--max-usd off` | `/budget off` |
| Subscription (no cap; spend is a list-price estimate) | — | `--max-usd subscription` | `/budget subscription` |
| Wall clock | `OC_MAX_MINUTES` | `--max-minutes 8h` (also `45`, `90m`, `2d`, `off`) | `/limits time 8h` |
| Steps per turn (root), 1–1000 | `OC_MAX_STEPS` | `--max-steps 100` (`default` clears) | `/limits steps 100` |
| Steps per sub-agent turn | `OC_MAX_SUBAGENT_STEPS` | — | — |
| Tideline (% of context) | `ledger.tideline.percent` (config) | `--tideline 50%` | `/tideline 50%` |
| Tideline (fixed tokens) | `ledger.tideline.tokens` (config) | `--tideline 60k` | `/tideline 60k` |
| Tideline block (what one cut removes) | `ledger.tideline.block` (config: a token count, or a share of the line as `"35%"`; unset = 70 % of the line) | — | — |

**Precedence:** slash command / flag > environment variable > built-in default. Flag and slash
values are stored on the session, so they survive `--continue`.

The tideline's old names still work as aliases: `--shelve`, `/shelve` and `ledger.shelve`.

## The step cap is a boundary, not a stop

`OC_MAX_STEPS` (default 300) caps the steps in one root turn, but a root turn that reaches it
**auto-continues** into a fresh turn — no budget or clock needs to be set. The ledger cuts by
blocks after every step, so even one long turn stays under the tideline (see
[06 · Session ledger and export](/oceancode/session-ledger-and-export#the-tideline--tideline)). `0` is
rejected: bound a long run with a budget or a clock instead.

Sub-agents are different: they simply **stop** at `OC_MAX_SUBAGENT_STEPS` (default 300), so a
raised root cap does not deepen them. A stopped sub-agent hands back its work: the lead gets its last
text followed by `stopped at the step cap; resume to continue`, and resuming the sub-agent (its
`task_id`, or a message to it) gives it a fresh 300 steps. Inside a workflow the same stop still
counts as unsuccessful: `success` is false and `stop_reason` is `max_steps`.

## Output tokens

`OC_MAX_OUTPUT_TOKENS` overrides the per-request output-token cap in either direction, clamped
to the model's own published maximum. It bounds how much a single model response may emit, not
the run as a whole; leave it unset to use the model's full maximum.

## What happens at a bound

Exceeding a bound is a **clean, non-error stop**, distinct from a crash, and the ledger records
it as a step-limited turn. The internal stop reasons are `BudgetExceeded` and `TimeExceeded`.
Over the Ocean tunnel the response carries a typed `stopped_by`:

| Bound reached | `stopped_by` |
|---|---|
| `OC_MAX_MINUTES` / `/limits time` | `time-exceeded` |
| `OC_MAX_USD` / `/budget` | `budget-exceeded` |
| step cap | `step-limit` |

## The plan drives the run

The model keeps its plan as a todo list (see [05 · Plan mode and todos](/oceancode/plan-mode-and-todos)).
A root turn that settles while any todo is still open does **not** end the run — the engine
works off the next open todos and drains again. A run ends only when:

- the work settles with no open todo; or
- a spend or clock bound trips; or
- you interrupt it; or
- the **no-progress guard** fires — two consecutive continued turns that leave the open and
  completed counts unchanged stop the run cleanly with a "Run stopped" toast.

So a model that never plans and never settles runs a few turns and stops; one that plans and
makes progress runs until its list is done or a bound trips.

While a background sub-agent of the session is still working, a settled turn is not worked
off: the sub-agent is what moves the plan, and its result re-drives the session when it lands.
The work-off resumes in that re-driven turn.

Such a turn ends as **waiting**, not finished, and says on what. The pager's end-of-turn line
reads `Worked for 6m38s · waiting for sub-agent "Build the mock" · 2 plan rows open · continues
when it lands`; an ACP client gets `_meta.oceanStopReason = "waiting"` and `_meta.waitingNote`
on the prompt response, and the session's log holds a durable `session.waiting` row. When the
sub-agent lands, the engine starts the next turn itself (a durable `session.resumed` row) and
the pager draws it as it runs; the sub-agent's result row tells the model how long the run
paused, so it can say so when asked. That turn shows as running from its first instant: an ACP
client gets `ocean/queue/changed` naming it as running before its first frame, and the pager's
status row reads what a turn you started would (`Waiting for response…` during a silent think,
`Thinking…`, the running tool, the sub-agent it waits on). Every turn the engine starts this way
gets its `session.resumed` row, including the follow-up that answers a result which landed while
a turn was running. A message you send while a turn is ending as waiting is answered as its own
turn and streamed under its own prompt id. The restart is guaranteed:

- a sub-agent that **fails or crashes** still reports, as an error result, and the run goes on;
- a sub-agent **you stop** (`ocean/subagent/cancel`, or `esc` `esc` on the session) does not
  restart the run; if plan rows are left open, the session records why
  (`reason: user_stopped_child`) and the next message you send continues it;
- if the **engine exits** while a sub-agent works (a quit, a crash), the next time the session
  is opened its lost sub-agents are reported as errors ("the engine restarted while it worked")
  and the run goes on. A session whose engine is still running elsewhere is left to it.

`oceancode run` waits for its background sub-agents and the turns their results start before
it exits.

## The Jev supervisor (opt-in)

With the Jev decision layer on (see [13 · Jev decisions](/oceancode/jev-decisions)), a supervisor
reads a root turn's clean finish and, before a `bash` call whose exact command already ran twice
among the prompt's last six, asks whether it would bring anything new (the no-progress check).
With `jev.supervisor = "act"` it may keep a finish from ending the turn —
a synthesized continuation, at most `jev.supervisor_max_continuations` per human prompt
(default 1), only when the goal is clearly not complete and progress is real. Two consecutive
no-progress answers send one steer message; four stop the run — under headless `--auto` it ends
with stop reason `no_progress` (exit 5), elsewhere you are asked *Continue* or *Stop*. With
`jev.check = "act"`, headless `--auto` also ends a run right after a check verified a pass. It
never moves a bound: the spend, clock and step bounds above, the exact-repeat doom-loop trip, the
hook cap and the no-progress guard decide exactly as they do without it. In `shadow`, the default
once the layer is enabled, it only records what it would have done.

## Stopping a run stops all of it

`esc` `esc` (or the palette's abort) stops the session's own drain at its next step boundary,
and every sub-agent stops with it — foreground ones immediately, background ones cancelled by
the parent. A late background result is recorded as cancelled and never restarts the run.

## Background jobs and long waits have no clock of their own

A job the model starts — `bash` with `background: true`, a `monitor`, a `loop`, a `scheduler` —
runs until it ends or the model calls `job_kill`. It dies with the run only when the run has a
wall clock (`OC_MAX_MINUTES` / `/limits time`); without one a build, a watch or a server runs for
hours or days, and `job_output` reads it back whenever the model asks. (Until 2026-10-05 every
job was reaped ten minutes after launch while the run had no clock at all.)

A job that ends on its own — done or failed, not killed — reports back. Its result (state,
command, the last 2,000 bytes of output) is posted to the session that started it as a
`<job id=… state=…>` message and a new turn starts, the way a background `task` reports; a turn
already running gets exactly one follow-up turn. So the model does not have to sleep and poll to
learn a job finished, and a turn that stopped polling still hears about it. A job a sub-agent
starts wakes no one.

Polling is not a loop either. The doom-loop trip ends a turn only after three consecutive fast
calls with the same input **and** the same result. A repeat whose output moved (a row count, a
timestamp, a log line) or a call that ran 30 seconds or more (a `sleep 300; check`) resets the
count to zero. When the trip does fire, the turn ends with stop reason `doom_loop` and the final
text `Stopped by the doom-loop guard: …` stays in the transcript, not only in a passing toast.
(Until 2026-10-24 three identical `sleep 298; wc -l results.jsonl` polls of a live benchmark
tripped it and the turn went quiet with no visible reason.)

A long wait does not lose the prompt cache either. Every request's cache markers carry the
provider's one-hour TTL, and while a step waits — on a sub-agent, on a long `bash` — or the
session idles, the engine re-reads the last request every `OC_CACHE_KEEPALIVE_MINUTES`
(default 50) so that clock restarts: a cache read of the prefix (`session.cache.touched`, cents)
instead of re-writing it when the wait ends. It stops after `OC_CACHE_KEEPALIVE_MAX` touches (default 48, ≈ 40 h; past a model's break-even — 80 touches on Fable 5.1, 20 on a 0.1× model — a re-write is the cheaper bet). A touch that fails is sent once more a few minutes later before the keep-alive gives up, and pressing Esc on a running turn does not stop it — the next prompt reads the same prefix. See [02 · Configuration](/oceancode/configuration).

## See also

- [02 · Configuration](/oceancode/configuration) — the `OC_MAX_*` variables in one table.
- [07 · Headless and server](/oceancode/headless-and-server) — the `--max-*` flags on `oceancode run`.
