Execution Limits

Three knobs bound every run โ€” deadline, stuck, cost_usd โ€” one schema at every scope, and one command that tells you what is in force and where it came from.

mur limits develop-rust                 # every bound for a fleet, with its source
mur limits rustsmith --json             # the same for an agent, for tooling
mur fleet limits develop-rust --deadline 3h --stuck 20m
mur agent limits rustsmith --stuck off
mur limits --global --deadline 4h       # config.yaml, edited as text
mur limits develop-rust --unset cost_usd

The three knobs

knobmeaningbuilt-in default
deadlinewall clock for the unit of work โ€” 30s, 5m, 2h, 1h30m, 1d30m for a single task, 1h for a fleet run
stuckhow long with no real progress before the run stops (unattended) or warns (attended); off disables10m
cost_usda spend cap โ€” only on a metered modelnone

They live in a limits: block with the same shape at three scopes, and the inner scope replaces a key it sets; nothing adds two caps together:

# ~/.mur/config.yaml            # ~/.mur/fleets/<name>/fleet.yaml     # ~/.mur/agents/<name>/profile.yaml
limits:                         limits:                              limits:
  deadline: 4h                    deadline: 2h                         stuck: off
  stuck: 20m

A per-call flag (mur fleet run --loop --deadline 15m) is the innermost scope of all.

Attended is not unattended

The axis is who is watching, not local versus cloud.

attended (murmur, Hub chat)unattended (mur agent send, schedules, channel/delegate, fleet loops, daemon auto-run)
deadlineignoredrequired โ€” built-in if nothing is set
stuckone warning in the live transcript: โš  no progress for 10m โ€” Esc to stopstops with stuck and the last three tool calls
cost_usdignoredenforced when set and the model is metered
hard stopEscdeadline / stuck / cost / mur fleet stop

Progress is a file write through any tool, a channel event authored by an agent, or a tool call whose name and arguments differ from the previous iteration's. A text-only model turn is not progress; the same call again is not progress. That is the "reasoning in circles" and the "retrying the same thing" shape, and those are exactly what the stuck clock exists to end.

The scope that launched owns the budget

A fleet delegating a task with twelve minutes left on its clock hands the member a twelve-minute deadline, not a fresh half hour. There is no per-task cap inside a fleet run; the fleet's limits: is the whole budget.

What no longer stops a run

  • Iteration caps (hitl.max_iterations, loop.max_iterations, --max-iterations) and token budgets (hitl.max_tokens) โ€” loaded, ignored, and named once at agent start and by mur limits:
stale: hitl.max_iterations: 800 in profile.yaml โ€” IGNORED since 2.79 โ€” remove it; the bounds are limits: deadline / stuck / cost_usd
  • loop.budget_usd still works: it is read as limits.cost_usd until you rewrite the file; mur fleet limits writes the new key.
  • The runtime keeps an iteration counter as a diagnostic, with a 10 000 ceiling that turns a runaway bug into a stop with a reason. It is not a setting.

Local models cost nothing

A cost cap on a fleet that runs on local or subscription models is reported as does not apply and never enforced โ€” that fleet is bounded by its deadline. A billable fleet with a deadline and no cost cap is allowed, but told so at start:

โš  billable, no cost cap โ€” bound is the deadline (2h); spend is reported per iteration at $0.05/1k โ€” set --budget-usd to cap it

Mark a local model with billing: local in models.yaml when its provider is not one MUR already knows to be local.

Long work returns a handle

Work duration is not a timeout. fleet_run (the agent tool) and parallel_jobs (the MCP tool) answer within a second:

Poll mur_job_status <run_id> โ€” it reports the semantic state (running / blocked / done / failed / stopped, or abandoned once a dead run is past its grace window) and a liveness verdict (alive / stalled / dead) โ€” or mur fleet status <fleet>; the result lands in the fleet's channel, and the murmur rail and the deep-research panel show it live. mur fleet run --run-id <id> records a run under an id you chose, which is how the tools mint theirs. The MCP per-call timeout stays 120 s on purpose: a tool that needs longer must return a handle, and raising the timeout is the wrong fix for the next twenty-minute tool.

A parallel_jobs run lives inside the MCP server process that dispatched it; if that process exits, the run ends.

No clock ends a live reply

The same rule reaches the model connection: nothing cuts a reply off by elapsed time. There is no total request timeout, so a reply that is still arriving keeps arriving โ€” which matters most for a local model that spends a minute loading weights and evaluating a long prompt before its first token.

What is bounded is silence, and with two numbers rather than one, because cold start and a dead socket look identical from outside:

BoundDefaultCovers
MUR_LLM_FIRST_DELTA_TIMEOUT_SECS300 sthe wait for the first chunk โ€” a cold local model
MUR_LLM_IDLE_TIMEOUT_SECS120 sthe gap between chunks, once it is emitting

A stream that goes quiet past its bound does not fail the turn. Whatever arrived is kept and visibly marked, so a truncated reply never reads as a complete one:

The first half of the answer that did arrive
[output truncated: the model stopped sending]

and the settlement card names the bound that bit, not a generic error:

โš  stopped at stream interrupted (0 iterations) โ€” output may be incomplete ยท
  the model stopped sending mid-reply โ€” ask it again; if this repeats on a
  slow local model, raise MUR_LLM_IDLE_TIMEOUT_SECS for that agent

Nothing usable assembled is a plain timeout instead, which is retried and then routed around by the fallback chain โ€” safe precisely because no answer text reached you to be duplicated. A tool call the model was still spelling out when the stream stopped is dropped, never run with half-finished arguments.

Raise either bound for a slow local box. A value of 0 is refused and the default kept: a bound a stray =0 can switch off is not a bound.

Requests that are not streamed have no socket clock at all โ€” before the first byte, "still thinking" and "hung" cannot be told apart โ€” so they are governed by the turn's deadline and by cancellation, exactly as above.

Liveness is a heartbeat

While a turn runs โ€” through model inference included โ€” the agent runtime sends a turn/heartbeat frame every 30 s on the connection that dialed it. The dial gives a beating peer 90 s of silence before failing with stopped responding โ€” no frame for 90s (last: <time> ยท turn/heartbeat); a router that thinks for ten minutes is alive the whole time. An agent still on an older runtime (before proto 2) keeps the old 600 s idle timeout, so a mixed-version fleet does not start failing mid-upgrade โ€” restart agents (mur update --restart-agents) to get them on the short one.

The beat does not depend on anything the caller sent. It used to be skipped when a caller omitted the optional task_id โ€” which mur agent send does โ€” so a healthy turn on that path emitted no proof of life at all and tripped the 90 s check while the agent sat waiting on the model. The runtime now mints the id itself, so liveness is never a caller courtesy.

Fail at dispatch, not after the budget

A fleet can declare what its work needs:

# ~/.mur/fleets/develop-rust/fleet.yaml
needs:
- write_file
- edit_file
- bash

Every delegate receives that list and checks it against its own tool inventory and policy โ€” the same tools: rules the approval gate reads โ€” before its first model call. A member missing one fails at once:

cannot start: rustsmith has no write_file โ€” mur agent perm tool-allow rustsmith write_file

No needs:, no preflight. And any authorization refusal (not authorized: โ€” the fleet_run allowlist, parallel_jobs.targets, a tool policy Deny) is told to the model once and that tool is withdrawn for the rest of the turn, so a turn cannot spin on the same refusal three times.

Every stop names itself

The settlement card and the fleet rail carry the reason and the remedy:

  โš  stopped at deadline (17 iterations) โ€” output may be incomplete ยท raise it: mur limits rustsmith --deadline <1h>

See Settlement and Fleet Loops.