@polyweave/flex (1.0.5)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/flex@1.0.5"@polyweave/flex": "1.0.5"About this package
@polyweave/flex
FLEX (Feedback Learning from EXperience) as a bt-harness tool. Processes a dataset through a four-stage pipeline — retrieve→explore→score→update — building a golden/warning experience library that agents learn from.
Published as @polyweave/flex.
Synopsis
import { createFlexTool } from '@polyweave/flex';
const tool = createFlexTool({
apiKey: process.env.API_KEY,
model: 'deepseek-v4-flash',
dataset: ['Write a sorting function', 'Handle null inputs', 'Add error logging'],
retriever: myRetrieverAdapter,
explorer: myExplorerAdapter,
scorer: myScorerAdapter,
updater: myUpdaterAdapter
});
tool.sink.write({ type: 'TOOL_CALL', content: { dataset: myItems } });
tool.source.pipe(mySink);
Install
npm install @polyweave/flex
Why
LLM agents that learn from their own outputs need a structured way to accumulate and retrieve experiences. FLEX provides a behavior-tree-driven experience accumulation framework: each dataset item flows through retrieve (find relevant past experiences), explore (produce output using those experiences), score (judge output quality), and update (promote good outputs to "golden", flag problematic ones as "warning"). The library persists across iterations, deduplicates entries, and is bounded by configurable capacity. Each stage can be injected with custom adapters or falls back to default behavior.
API
createFlexTool(options)
Creates a Polyweave frame duplex tool implementing the FLEX pipeline. Driven by TOOL_CALL initialization and TICK frames for step-by-step item advancement. Uses a bt-harness with the FLEX_SPEC behavior tree and an in-memory document store (or externally provided state stores).
createFlexTool(options?: Object): { source, sink, _toolName, getLibrary, close }
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
options.apiKey |
string |
No | — | API key for LLM adapter calls |
options.model |
string |
No | 'deepseek-v4-flash' |
Model name for agent and evaluator |
options.dataset |
Array |
No | [] |
Array of items (strings or objects) to process. Each item stored as { item } in document section flex/item/{i} |
options.retriever |
Function|Object |
No | — | Adapter for experience retrieval. Falls back to returning current library directly |
options.explorer |
Function|Object |
No | — | Adapter for generating output from item + retrieved experiences. Falls back to passthrough (stores output/experiences/success) |
options.scorer |
Function|Object |
No | — | Adapter for scoring output quality. Falls back to binary (success → 1, otherwise 0) |
options.updater |
Function|Object |
No | — | Adapter for determining library updates. Falls back to direct application of provided experiences |
options.initialLibrary |
Object|Array |
No | — | Initial experience library. If array, treated as golden entries. If object: { golden: [...], warning: [...] } |
options.state |
Object |
No | — | { docStore, textStore } for external persistence. If docStore not provided, creates an in-memory one |
options.maxGolden |
number |
No | 200 |
Maximum golden entries retained (oldest truncated) |
options.maxWarning |
number |
No | 200 |
Maximum warning entries retained (oldest truncated) |
options.createAdapter |
Function |
No | — | Custom adapter factory |
options.adapterConfig |
Object |
No | — | Adapter factory configuration |
options.debug |
boolean |
No | false |
Enable debug output (stored as _debug) |
Returns: An object with:
source— push-stream source for output (emits TOOL_RESULT, ERROR, DESCRIPTION)sink— push-stream sink (see frame protocol below)_toolName—'flex'getLibrary()— Returns the current{ golden: [...], warning: [...] }experience library (live reference)close()— Closes the internal document store viadocStore.close()
Frame Protocol
Input frames (sink receives):
| Frame Type | Description |
|---|---|
TOOL_CALL |
Initiates the FLEX pipeline. Content may include { dataset } to override the constructor dataset. Rejects with ERROR if already running. |
TICK |
Advances the bt-harness tick. On IDLE state, increments @itemIndex, updates the current item, and cancel() then re-runs the loop for the next item |
CANCEL |
Calls cancel() on the current harness, marks not-running, clears references |
ABORT |
Calls abort() on the current harness, marks not-running, clears references |
DESCRIBE |
Responds with DESCRIPTION: name 'flex', capabilities ['write', 'execute'], schema { dataset: { type: 'array' } } |
Output frames (source emits):
| Frame Type | Description |
|---|---|
TOOL_RESULT |
Pipeline completed (or failed). Content: { library, itemCount, itemsProcessed, failed: boolean } |
ERROR |
Emitted for: already running, harness validation failure (!harness.valid), parse errors (harness.errors) |
Internal tool calls exposed to agents:
| Tool | Arguments | Description |
|---|---|---|
flex_read |
{ sectionId } |
Reads any document section text |
flex_retrieve |
{ item, library } |
Retrieves relevant experiences. Without adapter: stores current library in flex/retrieved |
flex_explore |
{ item, output, experiences, success } |
Generates output. Without adapter: stores output/experiences/success in document sections |
flex_score |
{ score, output, success, item } |
Scores output. Without adapter: success ? 1 : 0. If output not provided, reads from flex/output |
flex_update |
{ experiences, output, score, success, item } |
Applies experience updates to library. Without adapter: directly applies experiences array |
flex_search |
{ query, limit } |
Searches indexed experiences via textStore. Only registered if textStore is provided. Returns { results: [...] } |
Adapter Pattern
Each adapter (retriever, explorer, scorer, updater) can be either:
- A function:
(payload, callback) => void— called viainvokeMaybeFramefrom@polyweave/loop-utils. Callscallback(error, result). - A duplex object:
{ source, sink }— thesink.write()receives a TOOL_CALL frame, and results are read fromsource.
Experience Library Structure
{
golden: [
{ id: 'exp-...', text: '...', metadata: { zone: 'golden' } },
// ...
],
warning: [
{ id: 'exp-...', text: '...', metadata: { zone: 'warning' } },
// ...
]
}
Entries are deduplicated by (zone + ':' + text).toLowerCase(). Libraries are bounded by maxGolden and maxWarning (oldest entries truncated).
Error Conditions
ERROR frames are emitted (never thrown) for:
- Already running — when TOOL_CALL received while pipeline is active; message:
'Already running' - Harness validation failure — when
!harness.valid; message: joinedharness.errors - Adapter errors — each stage's
invokeMaybeFramecallback receives an error; emitted as TOOL_RESULT with{ error: err.message || String(err) }
FLEX_SPEC
The behavior tree specification string. Exported for inspection.
Symbols:
@itemIndex(number, starts 0) — increments after each successful update@complete(boolean, starts false) — set by scheduler when all items processed
Postcondition: @complete == true
Tree structure:
root = sequence {
retrieve (flex_read + flex_retrieve + flex_search)
explore (flex_read + flex_explore + flex_search)
score (flex_read + flex_score)
update (flex_read + flex_update, @itemIndex++)
}
The harness uses maxTreeLoops: 0 and relies on the scheduler (in handleExternalTick) to advance items by canceling and re-running the tree per item.
Examples
// With external state and pre-populated library
import { createDocumentStore, createTextStore } from '@polyweave/state';
const tool = createFlexTool({
apiKey: process.env.API_KEY,
state: { docStore: createDocumentStore(), textStore: createTextStore() },
initialLibrary: {
golden: [{ text: 'Use guard clauses for null checks', metadata: { zone: 'golden' } }],
warning: [{ text: 'Avoid deep nesting in sort functions', metadata: { zone: 'warning' } }]
},
dataset: trainingItems,
retriever: (payload, callback) => callback(null, myLibrary)
});
// Tick-driven operation with item advancement
tool.sink.write({ type: 'TOOL_CALL', content: {} });
tool.sink.write({ type: 'TICK' }); // process one item's pipeline
tool.sink.write({ type: 'TICK' }); // next item
tool.sink.write({ type: 'CANCEL' }); // cancel mid-run
// With custom adapter functions
const tool = createFlexTool({
dataset: myItems,
retriever: (payload, cb) => cb(null, { golden: [], warning: [] }),
explorer: (payload, cb) => cb(null, { output: 'result', success: true }),
scorer: (payload, cb) => cb(null, 0.85),
updater: (payload, cb) => cb(null, [{ text: 'new golden rule', metadata: { zone: 'golden' } }])
});
Tests
npm test
Runs: node --test test/*.test.js
Covers: tool instantiation with various options, FLEX pipeline execution over multi-item datasets, experience library accumulation and deduplication, golden/warning capacity bounds, adapter fallback behavior for each stage, tick-driven item advancement, CANCEL/ABORT handling, and library persistence across runs.
Requirements
- Node.js >= 22.0.0
- ESM only (
"type": "module") - Dependencies:
@polyweave/core,@polyweave/bt-harness,@polyweave/state,@polyweave/tools,@polyweave/loop-utils
Caveats
- Each adapter can be either a duplex object or a callback function. Duplex adapters are preferred for streaming support.
- The experience library is bounded by
maxGoldenandmaxWarning. Oldest entries are truncated when limits are exceeded. - Without a
textStore, theflex_searchtool is not registered and in-library text search is unavailable. - The behavior tree uses
maxTreeLoops: 0and relies on external TICK for item advancement. Each TICK cancels and re-runs the harness for the next item.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/bt-harness | * |
| @polyweave/core | * |
| @polyweave/harness-base | * |
| @polyweave/loop-utils | * |
| @polyweave/state | * |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |