agents: maxTurns is the only runaway-loop guard the host offers, and Cadence sets it nowhere #72

Open
opened 2026-07-28 18:34:15 +00:00 by crenshawdev · 0 comments
crenshawdev commented 2026-07-28 18:34:15 +00:00 (Migrated from github.com)

The gap

maxTurns is a supported subagent frontmatter field. Cadence sets it on none of its seven agents.

All seven set tools and effort and nothing else:

agent effort maxTurns
cad-assumptions-analyzer xhigh unset
cad-executor high unset
cad-plan-checker low unset
cad-plan-checker-high high unset
cad-planner high unset
cad-reviewer high unset
cad-verifier high unset

grep -rn maxTurns over the repo returns nothing outside a spike write-up.

Why it is worth a look

It is the only runaway-loop guard the host offers. Cadence's dispatch surface is (agent_name, prompt, model?), and of everything the frontmatter exposes, maxTurns is the single field that bounds how long a dispatch can run. Today nothing does.

This matters more after #63, not less. Raising roles to xhigh raises tool-call counts by design, since effort is a coverage dial and not only a thinking dial. A looping executor is already the failure that motivates the model argument in #63 ("a looping executor burns more than the model it saved"). There is currently no ceiling on that loop.

What this is not

Not a truncation mitigation. That was the neighbouring question in #63 and it is settled separately: truncation is per-response, turns are not, and the two do not interact. This issue exists only because the field turned up while enumerating what the frontmatter actually controls.

What has to be determined before setting anything

The behaviour on hitting the cap is unverified. Nobody has tested it here. Before a value goes in any agent file:

  1. What does a maxTurns hit produce? A structured return carrying partial work, or an error the orchestrator sees as a failed dispatch? This decides everything else. Cadence's executor reports a task manifest; a cap that discards it is a different feature from one that truncates it.
  2. Does the orchestrator distinguish a capped run from a failed one? If not, a cap could silently look like a crash and trigger the wrong recovery path.
  3. What value per role? These are not alike. A verifier reading 15 files and an executor implementing a multi-task phase have very different legitimate turn counts, and a cap set to the wrong one converts a slow success into a failure. The floor should come from observed turn counts in real runs, not from a guess. Transcript records carry per-response data, so this is measurable rather than estimated.

A cap that fires on legitimate work is worse than no cap. That is the reason this is a decision and not a one-line patch.

Files this would touch

agents/*.md (frontmatter), and whatever in cadence-core/bin/route.mjs interprets a dispatch outcome if a capped run needs distinguishing from a failed one.

  • #63, where the field surfaced. Independent of it: this can land before, after, or without the rigor ladder.
## The gap `maxTurns` is a supported subagent frontmatter field. Cadence sets it on none of its seven agents. All seven set `tools` and `effort` and nothing else: | agent | `effort` | `maxTurns` | |---|---|---| | `cad-assumptions-analyzer` | `xhigh` | unset | | `cad-executor` | `high` | unset | | `cad-plan-checker` | `low` | unset | | `cad-plan-checker-high` | `high` | unset | | `cad-planner` | `high` | unset | | `cad-reviewer` | `high` | unset | | `cad-verifier` | `high` | unset | `grep -rn maxTurns` over the repo returns nothing outside a spike write-up. ## Why it is worth a look It is the only runaway-loop guard the host offers. Cadence's dispatch surface is `(agent_name, prompt, model?)`, and of everything the frontmatter exposes, `maxTurns` is the single field that bounds how long a dispatch can run. Today nothing does. This matters more after #63, not less. Raising roles to `xhigh` raises tool-call counts by design, since effort is a coverage dial and not only a thinking dial. A looping executor is already the failure that motivates the model argument in #63 ("a looping executor burns more than the model it saved"). There is currently no ceiling on that loop. ## What this is not Not a truncation mitigation. That was the neighbouring question in #63 and it is settled separately: truncation is per-response, turns are not, and the two do not interact. This issue exists only because the field turned up while enumerating what the frontmatter actually controls. ## What has to be determined before setting anything The behaviour on hitting the cap is **unverified**. Nobody has tested it here. Before a value goes in any agent file: 1. **What does a `maxTurns` hit produce?** A structured return carrying partial work, or an error the orchestrator sees as a failed dispatch? This decides everything else. Cadence's executor reports a task manifest; a cap that discards it is a different feature from one that truncates it. 2. **Does the orchestrator distinguish a capped run from a failed one?** If not, a cap could silently look like a crash and trigger the wrong recovery path. 3. **What value per role?** These are not alike. A verifier reading 15 files and an executor implementing a multi-task phase have very different legitimate turn counts, and a cap set to the wrong one converts a slow success into a failure. The floor should come from observed turn counts in real runs, not from a guess. Transcript records carry per-response data, so this is measurable rather than estimated. A cap that fires on legitimate work is worse than no cap. That is the reason this is a decision and not a one-line patch. ## Files this would touch `agents/*.md` (frontmatter), and whatever in `cadence-core/bin/route.mjs` interprets a dispatch outcome if a capped run needs distinguishing from a failed one. ## Related - #63, where the field surfaced. Independent of it: this can land before, after, or without the rigor ladder.
Sign in to join this conversation.
No description provided.