Batch-mutate an array key in a single clone+write cycle.
Every scope.items.push(x) clones the entire array and commits it — O(N) per call.
For N mutations on an M-length array that is O(N×M). Use $batchArray to clone once,
apply all mutations inside fn, then commit once — O(M) total.
// Before: 1000 clones × growing array = O(N²)
for (let i = 0; i < 1000; i++) scope.items.push(i);
// After: 1 clone + 1 commit = O(N)
scope.$batchArray('items', (arr) => {
for (let i = 0; i < 1000; i++) arr.push(i);
});
fn receives a plain (non-proxy) mutable deep copy (structuredClone) of the
current array — "clone once" means the elements too. Mutating an element inside fn
(arr[0].n = 9) changes the copy and lands in the one committed write; the value in
state is never touched (9.22.0 — a shallow copy used to share the committed elements,
so an element edit changed committed state in place with no trace row).
Mutations inside fn are NOT tracked individually — only the final committed array
appears in the narrative as a single write. If the key does not exist or is not an
array, fn receives an empty array and the result is committed as the new value.
If fn throws, setValue is never called and state remains unchanged (atomic on
error). The exception propagates to the caller.
key is untyped (string) — TypeScript will not catch typos. arr is typed as
unknown[] because ScopeMethods is not parameterized by T; cast inside fn
when element types are known: (arr as string[]).push(x).
Stop the current execution context.
propagateBreak: true, the break signal (and its
optional reason) propagates up to the parent scope, terminating the
outer loop too. See SubflowMountOptions.propagateBreak.Optionalreason: string
Optional free-form string describing why the break
happened. Propagates to FlowBreakEvent.reason so recorders and
custom narratives can surface it, served under the run's redaction
policy. Defaults to undefined.
A call to an outside system, made inside this stage (P2): fn runs on the attempt runner under policy
(none = ONE attempt — still recorded), and every attempt is an AttemptRecord of kind 'call' on this
stage's node and on onAttempt, with its timing, outcome, category and decision. It runs under this stage
attempt's time and signal — pass t.signal on. target is a NAME from code (1–128 characters, no control
character), never a value. A malformed call (target, fn, policy) or one from an attempt that already ended
THROWS at the call, synchronously, like every scope refusal — it is the stage's code breaking its contract; a
call that runs and fails REJECTS with its last attempt's own error. A call that changes an outside system
declares it in options ({ touches, idempotencyKey }, P3): a touching call that may retry needs a key, every
record carries its effect, and a call that ends without an answer after any attempt of unknown effect rejects
with UnknownEffectError.
const quote = await scope.$call('stripe.quotes.create', (t) => stripe.quotes.create(body, { signal: t.signal }),
{ attempts: 3, timeoutMs: 10_000 });
Optionalpolicy: RetryPolicyOptionaloptions: CallOptionsOptionaldescription: stringOptionalinput: unknownOptionalinput: unknownFire a structured event to every attached recorder implementing
onEmit. The primary primitive for consumer-authored observability
events (LLM tokens, billing metrics, domain milestones, etc.) that
shouldn't live in scope state.
Synchronous, pass-through — delivered to recorders immediately, in call order. Zero-allocation when no recorders are attached.
Use hierarchical dotted names: '<namespace>.<category>.<event>'.
Examples:
'agentfootprint.llm.tokens''myapp.billing.spend''auth.check.denied'The library stays vocabulary-agnostic — no central registry, no reserved prefixes. Namespace prefixes prevent collisions across libraries and apps naturally.
If RedactionPolicy.emitPatterns matches the name, the payload is
replaced with '[REDACTED]' before dispatch. No recorder ever sees
the raw value for redacted event names.
Consumer-chosen event identifier (see naming convention).
Optionalpayload: unknown
Structured data to attach. Shape is up to the consumer; library treats it as opaque.
Proxy-synthesized: calls getValue(rootKey) then walks the nested path (lodash.get-style, no lodash dep). Not a direct delegation.
OptionalshouldRedact: booleanOptionaldescription: stringOptionaldescription: string
The attempt this stage function runs in (P1b):
attempt/of, thesignalto pass tofetchand SDK calls (it aborts at the attempt's cap, the run's deadline, or a cancellation), andtimeLeftMs()— the milliseconds before the run's deadline (Infinitywithout one; reading it with a deadline marks the stage's commituntrackedSources: ['env']). A stage with no retry policy and no deadline is attempt 1 of 1.