NOETIC
Framework
API Reference

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;
}
FieldTypeDescription
stepCountnumberNumber of iterations completed
tokens.inputnumberTotal input tokens consumed
tokens.outputnumberTotal output tokens generated
tokens.totalnumberTotal tokens (input + output)
elapsednumberElapsed time in milliseconds
costnumberAccumulated cost in USD
heldCostnumberUSD held for cut-off paid calls with no settled bill — see Paid Calls (optional)
lastOutputunknownRaw output from the last iteration
lastTextstringText output from the last iteration
historyunknown[]Previous outputs, oldest first; bounded by maxHistorySize (default 100, oldest dropped first)
depthnumberExecution nesting depth
lastStepMetaStepMeta | nullMetadata from the last step (optional)

Verdict

The return value from an Until predicate.

interface Verdict {
  stop: boolean;
  reason?: string;
  feedback?: string;
}
FieldTypeRequiredDescription
stopbooleanyesWhether to stop the loop
reasonstringnoWhy the loop stopped or continued; recorded on the loop.verdict span event
feedbackstringnoFeedback 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;
}>;
FieldTypeDescription
passbooleanWhether verification succeeded
feedbackstringFeedback for the next iteration (optional)

ConvergeConfig

interface ConvergeConfig {
  threshold?: number;
  embed?: EmbedFn;
  cache?: StorageAdapter;
}
FieldTypeDefaultDescription
thresholdnumber1Similarity threshold. At 1 (default), uses exact string equality. When embed is provided and threshold < 1, uses cosine similarity.
embedEmbedFn—Embedding function. When provided with threshold < 1, enables semantic convergence detection.
cacheStorageAdapter—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));

On this page