Skip to content
Tracon

LoopSettings

Namespace Tracon · Assembly Tracon.Abstractions.dll

Turns the harness loop on: the agent is re-invoked until a stop criterion says the work is finished.

public sealed record LoopSettings : IEquatable<LoopSettings>
Inheritance
object ← LoopSettings
Implements
IEquatable<LoopSettings>
Inherited Members
object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

An agent that keeps working until it writes ALL DONE, for at most five iterations:

new AgentDefinition
{
Name = "researcher",
Model = new ModelBinding { Provider = "openai", Model = "gpt-5" },
Harness = new HarnessSettings
{
Loop = new LoopSettings
{
Criteria = [new LoopCriterion { Kind = "completionMarker", Marker = "ALL DONE" }],
MaxIterations = 5,
},
},
};

A reviewer loop that fails the run when its judge is still not convinced after three drafts, and keeps the last draft when the judge itself cannot be reached:

new LoopSettings
{
Criteria = [new LoopCriterion { Kind = "aiJudge", JudgeCriteria = ["The draft is ready to publish"] }],
MaxIterations = 3,
OnExhausted = LoopOutcome.FailRun,
OnCriterionError = LoopOutcome.ReturnLastResponse,
};

The loop is off while HarnessSettings.Loop is null, and the harness behaves exactly as it did before this setting existed. Nothing turns it on implicitly.

This is not HarnessSettings.MaximumIterationsPerRequest. The two numbers count different things and are deliberately kept in separate types so they cannot be read as one:

SettingWhat it bounds
`HarnessSettings.MaximumIterationsPerRequest` The harness's *inner* tool-calling loop inside ONE agent invocation. It is a runaway-safety ceiling, not a stop criterion.
`MaxIterations` The *outer* loop that re-invokes the whole agent after a criterion says the work is not finished. It bounds how many times that may happen.

Criteria are evaluated in order, and the first one that asks for another iteration wins — the remaining criteria are not evaluated that iteration. The loop therefore stops only when every criterion is satisfied. Put the cheapest criterion first: an aiJudge placed after a completionMarker costs nothing on the iterations the marker already keeps going.

Every completed iteration writes a RunEventType.LoopIterationCompleted event to the run, so the loop is visible in the run record instead of hidden inside a caller’s own code.

How the loop ends when no criterion is satisfied. By default the run returns the last response as a success, whether the ceiling was reached or a criterion threw. LoopSettings.OnExhausted and LoopSettings.OnCriterionError can make either case fail the run instead, so a workflow does not hand a response no criterion accepted to its next node.

public LoopSettings()

The iteration ceiling applied when MaxIterations is null.

public const int DefaultMaxIterations = 10

int

The ceiling is never left open. A criterion that can never be satisfied — a marker the model does not write, a judge that is never convinced — turns an open loop into a bill the consumer only learns about from an invoice, so Tracon applies its own ceiling instead of leaving the number to the framework’s default.

Gets the stop criteria, evaluated in order. At least one is required.

public required IReadOnlyList<LoopCriterion> Criteria { get; init; }

IReadOnlyList<LoopCriterion>

Gets a value that starts every iteration from a fresh context instead of carrying the previous iterations’ messages forward.

public bool FreshContextPerIteration { get; init; }

bool

Off by default. Turning it on changes what the agent remembers between iterations: the agent no longer sees its own earlier answers, only the original request and the criterion’s feedback. Use it when repeated context is what makes the agent repeat itself; leave it off when the work builds on the previous iteration.

Gets the upper number of loop iterations. null takes DefaultMaxIterations. Must be greater than zero.

public int? MaxIterations { get; init; }

int?

Gets what happens when a criterion throws while it is evaluated. LoopOutcome.ReturnLastResponse by default.

public LoopOutcome OnCriterionError { get; init; }

LoopOutcome

By default the loop stops and the last response is returned; the LoopIterationCompleted event names the criterion in failedCriterion. With LoopOutcome.FailRun the run fails with the criterion’s own error - an unreachable judge provider stays a provider error, not a rejected response - and the event also carries failsRun.

The run’s own cancellation is never a criterion error: a cancelled run ends as cancelled in both modes.

Gets what happens when the iteration ceiling is reached while a criterion still asks for another iteration. LoopOutcome.ReturnLastResponse by default.

public LoopOutcome OnExhausted { get; init; }

LoopOutcome

With LoopOutcome.FailRun the run fails with error type loop_unsatisfied (TraconLoopUnsatisfiedException, RunErrorClass.LoopUnsatisfied), and the last LoopIterationCompleted event carries failsRun.

The last iteration is evaluated in this mode. By default the turn that reaches the ceiling is not evaluated, because nothing is left to decide. Failing the run without evaluating it would refuse a last response that meets every criterion, so with LoopOutcome.FailRun the criteria run once more after that turn - one more evaluation, which for an aiJudge criterion is one more model call. The number of agent turns stays LoopSettings.MaxIterations. With LoopSettings.MaxIterations set to 1 the single turn is evaluated too; by default it is not.

A streaming caller receives the last turn’s text before the failure: the decision is made after that turn has streamed.

public override bool Equals(object? obj)

obj object?

bool

public bool Equals(LoopSettings? other)

other LoopSettings?

bool

public override int GetHashCode()

int

public override string ToString()

string

operator ==(LoopSettings?, LoopSettings?)

Section titled “ operator ==(LoopSettings?, LoopSettings?)”
public static bool operator ==(LoopSettings? left, LoopSettings? right)

left LoopSettings?

right LoopSettings?

bool

operator !=(LoopSettings?, LoopSettings?)

Section titled “ operator !=(LoopSettings?, LoopSettings?)”
public static bool operator !=(LoopSettings? left, LoopSettings? right)

left LoopSettings?

right LoopSettings?

bool