@polyweave/loop-utils (1.0.5)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/loop-utils@1.0.5"@polyweave/loop-utils": "1.0.5"About this package
@polyweave/loop-utils
Shared utilities for self-improving optimization loops — Pareto-front computation, dataset reservoir sampling, experience library CRUD, sequential iteration helpers, async/sync/frame invokers, and Polyweave state-path read/write.
Synopsis
import { computeParetoFront, sampleDataset, applyExperienceOperations } from '@polyweave/loop-utils';
const front = computeParetoFront(candidates, 'paretoScores');
const sample = sampleDataset(dataset, 10);
const next = applyExperienceOperations(library, [
{ operation: 'ADD', entry: { text: 'new experience' } },
{ operation: 'UPDATE', id: 'exp-1', content: 'updated text' },
{ operation: 'DELETE', id: 'exp-2' }
]);
Install
npm install @polyweave/loop-utils
Why
Self-improving optimization loops (training, GRPO, DPO, sieve-based RL) need non-trivial iteration control, multi-objective selection, experience library management, dataset sampling, and state-path plumbing. These utilities are shared across @polyweave/training, @polyweave/optimization, @polyweave/self-improving, and @polyweave/nursery. Extracted to avoid 4 copies of computeParetoFront.
API
From utils.js
runSeries(items, iterator, done)
Sequentially iterates items using a callback-based iterator.
Signature: runSeries(items: Array<*>, iterator: Function, done: Function): void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
items |
Array |
Yes | — | Items to iterate. |
iterator |
(item, next) => void |
Yes | — | Called for each item. Calls next(err?) to proceed. |
done |
(err?) => void |
Yes | — | Callback after all items complete, or on first error. |
Returns: void. done is always called exactly once.
Throws: Does not throw. Errors are passed to done(err).
runSeriesCollect(items, iterator, done)
Like runSeries but collects results into an array.
Signature: runSeriesCollect(items: Array<*>, iterator: Function, done: Function): void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
items |
Array |
Yes | — | Items to iterate. |
iterator |
(item, next) => void |
Yes | — | Called per item. Calls next(err, result?). |
done |
(err?, results?) => void |
Yes | — | Final callback. Receives (null, results) on success, (err) on error. |
Returns: void.
Throws: Does not throw. Errors are passed to done(err).
invokeMaybeSync(fn, args, done)
Invokes a function that may be sync (returns a value) or async (uses a callback). Resolves via callback.
Signature: invokeMaybeSync(fn: Function, args: Array<*>, done: Function): void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
fn |
Function |
Yes | — | A function fn(...args, callback). |
args |
Array |
Yes | — | Arguments to pass (the callback is appended). |
done |
(err?, result?) => void |
Yes | — | Receives result from sync return or async callback. |
Behavior: Calls fn(...args, finish). If fn returns a non-undefined value before finish is called, that value is used as the result. Otherwise waits for finish(err, result). Only the first resolution is used (subsequent calls to finish are no-ops).
Throws: Does not throw.
invokeMaybeFrame(handler, payload, done, options)
Invokes a function or frame handler (duplex), routing META frames and resolving TEXT, END, TOOL_RESULT, or META response streams.
Signature: invokeMaybeFrame(handler: Function|IFrameDuplex, payload: *, done: Function, options?: Object): void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
handler |
Function | IFrameDuplex |
Yes | — | A callback-style function or a frame handler duplex (with sink + source). |
payload |
* |
Yes | — | Payload passed to the handler. For frame handlers, becomes content.payload in the META frame. |
done |
(err?, result?) => void |
Yes | — | Receives the resolved result. |
options |
Object |
No | {} |
Options |
options.action |
string |
No | 'self-improving' |
The META frame action name. |
options.resolveFromFrame |
string |
No | undefined |
If 'tool_result', resolves with the content of the matching TOOL_RESULT frame. |
options.toolName |
string |
No | undefined |
When resolveFromFrame: 'tool_result', filters for a specific tool name. |
Behavior:
- If
handleris a function: calls it viainvokeMaybeSync(handler, [payload], done). - If
handleris a frame handler duplex: writes a META frame, then listens for completion on the source. Resolution path:TEXTframes accumulate into a buffer.ENDframe: resolves with accumulated text buffer, or ifresolveFromFrame: 'tool_result', with{ text, toolResults }.TOOL_RESULTframe: appends to toolResults array. IfresolveFromFrame: 'tool_result', resolves immediately with{ text, toolResults }.METAframe: resolves with the meta result. IfresolveFromFrame: 'tool_result', returns{ result, toolResults }.ERRORframe: rejects viadone(error).
Throws: If handler is neither a function nor a frame handler, calls done(new Error('Handler must be a function or frame handler')).
sampleDataset(dataset, size, rng)
Fisher-Yates shuffle reservoir sampling.
Signature: sampleDataset(dataset: Array<*>, size: number, rng?: Function): Array<*>
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
dataset |
Array |
Yes | — | Source array to sample from. |
size |
number |
Yes | — | Number of items to return. |
rng |
() => number |
No | Math.random |
Random number generator returning [0, 1). |
Returns: Array<*> — Sampled array.
Behavior:
- If
dataset.length === 0: returns[]. - If
size === 0(exactly zero): returns[]. - If
size >= dataset.length: returns a shallow copy (dataset.slice()). - Otherwise: Fisher-Yates shuffles a copy, returns the first
sizeitems.
Throws: Does not throw.
normalizeExperienceLibrary(library)
Shallow-copies experience entries from an array.
Signature: normalizeExperienceLibrary(library: Array<*>|*): Array<Object>
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
library |
Array | * |
Yes | — | Anything. If not an array, treated as []. |
Returns: Array<Object> — Each element is a shallow spread copy ({ ...entry }).
Throws: Does not throw.
applyExperienceOperations(library, operations)
Applies ADD, UPDATE, DELETE operations to an experience library.
Signature: applyExperienceOperations(library: Array<*>|*, operations: Array<Object>|*): Array<Object>
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
library |
Array | * |
Yes | — | Source library. Falsey values are normalized to []. |
operations |
Array | * |
Yes | — | Operations list. Falsey values are treated as []. |
Operation shapes:
| Operation | Fields | Effect |
|---|---|---|
ADD |
{ operation: 'ADD', entry: { id?, text, metadata? } } |
Adds entry if text exists and id doesn't already exist. Auto-generates id if missing. |
UPDATE |
{ operation: 'UPDATE', id, content } |
Updates entry text by id. If id not found, creates new entry with the content as text. |
DELETE |
{ operation: 'DELETE', id } |
Removes entry by id. |
Also recognizes op as alias for operation (checked via op.operation || op.op).
Returns: Array<Object> — The modified library (new array).
Throws: Does not throw. Invalid operations (null, undefined, missing text for ADD) are silently skipped.
computeParetoFront(candidates, scoreKey)
Returns the Pareto-optimal front from a candidate array using multi-objective dominance.
Signature: computeParetoFront(candidates: Array<Object>, scoreKey?: string): Array<Object>
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
candidates |
Array<Object> |
Yes | — | Array of candidate objects. |
scoreKey |
string |
No | 'paretoScores' |
Property name for the numeric score vector on each candidate. |
Returns: Array<Object> — Candidates that are not dominated by any other candidate.
Dominance rule: A dominates B iff all scores in A >= B and at least one is strictly >. Candidates missing the score key are excluded from the front (not compared as dominators, but skipped when checking if they are dominated).
Complexity: O(n^2) comparisons.
Throws: Does not throw.
From stateUtils.js
readStateValue(store, sectionId, pathValue, fallbackPath)
Reads a nested value from a Polyweave document store section.
Signature: readStateValue(store: Object, sectionId: string, pathValue: string|string[], fallbackPath?: string|string[]): *
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
store |
Object |
Yes | — | Must have a getSection(sectionId) method. |
sectionId |
string |
Yes | — | Section identifier. |
pathValue |
string | string[] |
Yes | — | Dot-separated string or array path. If falsely/empty and fallbackPath is provided, uses fallback. |
fallbackPath |
string | string[] |
No | 'data' |
Fallback path if pathValue is empty/falsy. |
Returns: * — The value at the path, or undefined if store is invalid, getSection returns null/missing, section.data is missing, or any intermediate path segment is missing.
Path normalization: String paths are split on '.'. Array paths are used directly. Fallback defaults to ['data'].
Throws: Does not throw.
writeStateValue(store, sectionId, pathValue, fallbackPath, value)
Writes a nested value to a Polyweave document store via DOC_UPDATE frame.
Signature: writeStateValue(store: Object, sectionId: string, pathValue: string|string[], fallbackPath?: string|string[], value: *): void
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
store |
Object |
Yes | — | Must have a sink.write(frame) method. |
sectionId |
string |
Yes | — | Section identifier. |
pathValue |
string | string[] |
Yes | — | Dot-separated string or array path. |
fallbackPath |
string | string[] |
No | 'data' |
Fallback path. |
value |
* |
Yes | — | Value to write. |
Returns: void. If store is invalid or store.sink.write is not a function, silently returns.
Frame emitted: { type: 'DOC_UPDATE', content: { sectionId, op: 'set', path, value } }.
Throws: Does not throw.
Tests
npm test
Covers: Pareto-front computation with dominance edge cases, reservoir sampling (size=0, size>=len, empty input), experience library operations (ADD, UPDATE, DELETE, idempotent ADD), sequential iteration (runSeries, runSeriesCollect), invokeMaybeSync dual-mode, invokeMaybeFrame with function and duplex handlers, state value read with dot-path and array-path, and state value write via DOC_UPDATE frame.
Requirements
- Node.js 22+
- ESM only
- Peer:
@polyweave/core - Dependency:
@polyweave/state(forstateUtils.js)
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/state | * |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |