Until Types
Type definitions for termination predicates, verdicts, snapshots, and combinators in Noetic.
Until
A termination predicate function. Called after each loop iteration to decide whether to stop.
type Until = (snapshot: Snapshot, run?: UntilRun) => Verdict | Promise<Verdict>;
/** What a loop hands its predicate so the predicate can run steps. */
interface UntilRun {
ctx: Context;
executeStep: ExecuteStepFn;
}A loop always passes run; any() and all() forward it. Predicates that only read the snapshot ignore it. A predicate that throws stops the loop with reason Predicate error: <message>, except that a cancelled error and a NoeticConfigError propagate and fail the loop. JSON workflows add an expr predicate that tests the last output with a JSONata expression; see Expression as a predicate. A loop's until (and each argument of any()/all()) may also be a decide step asking one boolean question — yes stops, no or a refusal continues. See Ending a loop with a decision.
Snapshot
The state visible to an Until predicate at the end of each iteration.
interface Snapshot {
stepCount: number;
tokens: {
input: number;
output: number;
total: number;
};
elapsed: number;
cost: number;
heldCost?: number;
lastOutput: unknown;
lastText: string;
history: unknown[];
depth: number;
lastStepMeta?: StepMeta | null;
}| Field | Type | Description |
|---|---|---|
stepCount | number | Number of iterations completed |
tokens.input | number | Total input tokens consumed |
tokens.output | number | Total output tokens generated |
tokens.total | number | Total tokens (input + output) |
elapsed | number | Elapsed time in milliseconds |
cost | number | Accumulated cost in USD |
heldCost | number | USD held for cut-off paid calls with no settled bill — see Paid Calls (optional) |
lastOutput | unknown | Raw output from the last iteration |
lastText | string | Text output from the last iteration |
history | unknown[] | Previous outputs, oldest first; bounded by maxHistorySize (default 100, oldest dropped first) |
depth | number | Execution nesting depth |
lastStepMeta | StepMeta | null | Metadata from the last step (optional) |
Verdict
The return value from an Until predicate.
interface Verdict {
stop: boolean;
reason?: string;
feedback?: string;
}| Field | Type | Required | Description |
|---|---|---|---|
stop | boolean | yes | Whether to stop the loop |
reason | string | no | Why the loop stopped or continued; recorded on the loop.verdict span event |
feedback | string | no | Feedback to pass to prepareNext for the next iteration |
Built-in Predicates
The until object provides factory functions for common predicates:
until.maxSteps(n)
Stops after n iterations with reason Reached max steps (n).
const stop: Until = until.maxSteps(10);until.maxCost(usd)
Stops when accumulated cost plus heldCost reaches the USD limit, with reason Cost $<cost> exceeded budget $<usd> (both to two decimals, e.g. Cost $1.02 exceeded budget $1.00). Counting the hold keeps a resumed run from spending what its cut-off attempt may already have spent. A cut-off call with no ceiling — an unbounded or unreadable dispatch — adds nothing to heldCost, so this predicate cannot see it.
const stop: Until = until.maxCost(1.0);until.maxDuration(duration)
Stops when elapsed time reaches the millisecond limit.
const stop: Until = until.maxDuration(60_000);until.noToolCalls()
Stops when the LLM responds without making any tool calls. Skips the first iteration (step count < 1).
const stop: Until = until.noToolCalls();until.verified(fn)
Stops when the verify function returns { pass: true }. The reason is Verification passed or Verification failed; feedback from failed verification is available in the verdict.
declare const checkOutput: VerifyFn;
const stop: Until = until.verified(checkOutput);until.converged(opts)
Stops when the output text is identical to the previous iteration's output.
const stop: Until = until.converged({ threshold: 1 });until.outputContains(marker)
Stops when the output text contains the given marker string.
const stop: Until = until.outputContains('DONE');until.custom(fn)
Wraps any Until function (identity passthrough for documentation purposes).
declare const fn: Until;
const stop: Until = until.custom(fn);VerifyFn
The verification function used by until.verified():
type VerifyFn = (output: unknown) => Promise<{
pass: boolean;
feedback?: string;
}>;| Field | Type | Description |
|---|---|---|
pass | boolean | Whether verification succeeded |
feedback | string | Feedback for the next iteration (optional) |
ConvergeConfig
interface ConvergeConfig {
threshold?: number;
embed?: EmbedFn;
cache?: StorageAdapter;
}| Field | Type | Default | Description |
|---|---|---|---|
threshold | number | 1 | Similarity threshold. At 1 (default), uses exact string equality. When embed is provided and threshold < 1, uses cosine similarity. |
embed | EmbedFn | — | Embedding function. When provided with threshold < 1, enables semantic convergence detection. |
cache | StorageAdapter | — | Persist the previous output vector across ephemeral invocations (e.g., Cloudflare Workers). |
Combinators
any(...predicates)
Returns an Until that stops when any predicate returns { stop: true }. Predicates run in order; the first stop verdict is returned as is. When none stops, the verdict carries every predicate's reason (joined with '; ') and feedback (joined with '\n'), so a failing until.verified() still hands its feedback to prepareNext.
declare function any<TContext = ContextData>(...predicates: Array<Until | StepDecide<TContext>>): Until;all(...predicates)
Returns an Until that stops when all predicates return { stop: true }. Predicates run in order; the first continue verdict is returned as is, with its reason and feedback. When all stop, the verdict carries every predicate's reason (joined with '; ', or 'All predicates satisfied' if none gave one) and feedback (joined with '\n').
Both combinators follow one rule: the combined verdict carries the reasons and feedback of the predicates that decided it, and leaves a field out when none of them set it.
declare function all<TContext = ContextData>(...predicates: Array<Until | StepDecide<TContext>>): Until;Both throw INVALID_UNTIL_DECIDER when built with a decide step that does not ask exactly one boolean question.
Example: Combining Predicates
import { all, any, until } from '@noetic-tools/core';
import type { VerifyFn } from '@noetic-tools/core';
declare const myVerifyFn: VerifyFn;
const stopEarly = any(until.maxSteps(10), until.maxCost(1.0), until.noToolCalls());
const strictStop = all(until.verified(myVerifyFn), until.maxSteps(3));