Polyweave

@polyweave/flex (1.0.5)

Published 2026-07-11 15:25:18 +00:00 by Dvorak

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 via docStore.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 via invokeMaybeFrame from @polyweave/loop-utils. Calls callback(error, result).
  • A duplex object: { source, sink } — the sink.write() receives a TOOL_CALL frame, and results are read from source.

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: joined harness.errors
  • Adapter errors — each stage's invokeMaybeFrame callback 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 maxGolden and maxWarning. Oldest entries are truncated when limits are exceeded.
  • Without a textStore, the flex_search tool is not registered and in-library text search is unavailable.
  • The behavior tree uses maxTreeLoops: 0 and 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 *

Keywords

polyweave flex feedback-learning behavior-tree
Details
npm
2026-07-11 15:25:18 +00:00
67
John Dvorak
SEE LICENSE IN LICENSE
latest
8.2 KiB
Assets (1)
flex-1.0.5.tgz 8.2 KiB
Versions (3) View all
1.0.5 2026-07-11
1.0.2 2026-07-10
1.0.1 2026-07-10