Polyweave

@polyweave/creativity (1.0.5)

Published 2026-07-11 15:22:50 +00:00 by Dvorak

Installation

@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/
npm install @polyweave/creativity@1.0.5
"@polyweave/creativity": "1.0.5"

About this package

@polyweave/creativity

Diversity-gated creative ideation pipeline that decorrelates prompts with @polyweave/sampler, fans them out through an injected LLM adapter, filters results by cosine-distance diversity, and iteratively anneals toward genuinely different territory via LLM-synthesized "bans" on pruned patterns.

Published as @polyweave/creativity.

Synopsis

import { createCreativityTool, filterDiverseIdeas, textToPseudoEmbedding } from '@polyweave/creativity';

// Standalone diversity filtering
const ideas = [
  { text: 'Solar panel windows for buildings' },
  { text: 'Photovoltaic glass facades' },
  { text: 'Tidal energy kites' },
  { text: 'Underwater current turbines' }
];
const result = filterDiverseIdeas(ideas, 0.35);
console.log(result.kept.length); // ~2

// Tool-mode: full pipeline with LLM generation + embedding-based filtering
const tool = createCreativityTool({
  llm: myLlmAdapter,
  embeddings: myEmbedAdapter,
  diversityThreshold: 0.35,
  defaultCount: 8
});

tool.source.pipe(mySink);
tool.sink.write({ type: 'TOOL_CALL', content: { arguments: { topic: 'sustainable energy' } } });

// Stream-level diversity transforms (no tool wrapping)
import { createDiversityTransform, createDiversityReducer } from '@polyweave/creativity';

const fanout = createDiversityTransform(llmAdapter, { count: 6, temperature: 0.9 });
const reducer = createDiversityReducer({ threshold: 0.3 });

source.pipe(fanout).pipe(reducer).pipe(outputSink);

Install

npm install @polyweave/creativity

Why

LLM responses cluster around familiar patterns — low-temperature sampling converges and high-temperature sampling produces gibberish. This package solves the "diversity problem" without sacrificing coherence: it decorrelates prompts to produce structurally varied inputs, fans each through the LLM, then prunes near-duplicates by embedding distance. After pruning, it asks the LLM to analyze the rejected outputs to synthesize "bans" — themes and patterns to avoid — which get injected into the next round's prompt. The result is a self-annealing creativity loop that pushes each iteration into genuinely different territory.

API

createCreativityTool(options)

Creates a Polyweave frame duplex tool. Send TOOL_CALL frames; receive PROGRESS, TOOL_RESULT, ERROR, and END frames in response.

Parameters:

Name Type Required Default Description
options.llm IFrameDuplex No null LLM adapter { source, sink } for text generation. Must implement the Polyweave frame protocol.
options.embeddings IFrameDuplex No null Embedding adapter { source, sink } for cosine-distance filtering. Falls back to trigram pseudo-embeddings when omitted.
options.diversityThreshold number No 0.35 Cosine distance threshold (0–1). Items closer than this value are pruned.
options.defaultCount number No 8 Number of decorrelated variants generated per round. Clamped to 2–50.
options.maxIterations number No 1 Number of diversity annealing rounds. Clamped to 1–5.
options.llmOptions object No {} Generation overrides: { temperature, maxTokens }.
options.embedOptions object No {} Embedding overrides: { model }.
options.tickDriven boolean No false If true, each round advances only on receiving a TICK frame.
options.generate boolean No true If false, skips LLM generation and labels variants with their sampler seed strings.

Returns: { source: IFrameSource, sink: IFrameSink, _toolName: 'creativity' }

Input frames (sink receives):

Frame type Purpose
TOOL_CALL Initiates creative generation. content.arguments must contain topic (string, required). Optional: context, count, diversityThreshold, maxIterations.
DESCRIBE Returns the tool's schema and capabilities via a DESCRIPTION frame.
CANCEL / ABORT Resets internal state.
TICK Advances one round (only when tickDriven is true).
END / QUIT Ignored.

Output frames (source emits):

Frame type Purpose
PROGRESS Emitted at each phase: starting, iteration_start, generation_complete, filter_complete, bans_synthesized, done.
TOOL_RESULT Emitted on completion. Contains success, topic, ideas (kept), pruned, pairs, keptCount, prunedCount, totalGenerated, iterations, diversityThreshold, embeddingMode, generateMode, banNotes, summary.
ERROR Emitted when LLM generation fails, embedding fails, or topic is missing. Content contains message.
END Emitted after TOOL_RESULT.
DESCRIPTION Response to DESCRIBE.

Error conditions:

Condition Error message
topic is missing, empty, or whitespace-only in TOOL_CALL args "topic is required" (emitted as ERROR + END, state remains null)
LLM generation fails (any variant in a round) err.message || "Generation failed" (emitted as ERROR, state reset to null)
Embedding adapter call fails err.message || "Embedding failed" (emitted as ERROR, state reset to null)

filterDiverseIdeas(ideas, threshold)

Synchronous diversity filter. Computes trigram pseudo-embeddings for each idea (unless idea.embedding is already an array), then iteratively keeps ideas whose cosine distance from all previously kept ideas exceeds threshold.

Parameters:

Name Type Required Default Description
ideas Array Yes Array of idea objects. Each may have text, idea, proposal, or be a raw string. May include an optional embedding (number array).
threshold number No 0.35 Cosine distance threshold (0–1).

Returns: { kept: Idea[], pruned: Idea[], pairs: Array<{a: number, b: number, distance: number}>, keptCount: number, prunedCount: number }

If ideas is not an array or has ≤ 1 element, returns { kept: ideas \|\| [], pruned: [], pairs: [] } without keptCount/prunedCount (only the non-array or ≤1 guard path omits count fields — the main code path always includes them).

filterDiverseIdeasAsync(ideas, threshold, getEmbedding, callback)

Async version of filterDiverseIdeas. Embeds each idea via the injected getEmbedding function, then applies the same greedy cosine-distance filter.

Parameters:

Name Type Required Default Description
ideas Array Yes Same as filterDiverseIdeas.
threshold number No 0.35 Cosine distance threshold.
getEmbedding (text: string, callback: (err, vector) => void) => void Yes Async embedding function. Must call callback(err, vector) with a numeric array or null.
callback (err, result) => void Yes Node-style callback. On success: callback(null, { kept, pruned, pairs, keptCount, prunedCount }).

Error conditions: If getEmbedding calls back with an error, that error is forwarded immediately via callback(err, null). Pending embeddings are cancelled.

textToPseudoEmbedding(text)

Generates a 128-dimensional unit-normalized trigram frequency vector. Used as a fallback when no embedding adapter is available.

Parameters:

Name Type Required Default Description
text any No Input text. Coerced to string, trimmed, and lowercased.

Returns: number[] — length 128 unit vector. Returns [] if text is empty after normalization.

Does not throw.

createDiversityTransform(adapter, options)

Fan-out stream transform implementing the IFrameTransform { source, sink } interface. Each input TEXT frame is decorrelated into N sampler variants, each is sent through the injected LLM adapter, and each result is emitted as a TEXT frame tagged with its sampler nonce (content.tag).

Parameters:

Name Type Required Default Description
adapter IFrameDuplex Yes LLM adapter { source, sink } used for each variant.
options.count number No 4 Variants per input TEXT frame. Clamped to 1–32.
options.temperature number No undefined Temperature override for each adapter call.
options.maxTokens number No undefined max_tokens override for each adapter call.
options.model string No undefined Model override for each adapter call.

Returns: IFrameTransform { source, sink }

Frame protocol:

Input → Output →
TEXT N × TEXT (one per variant, with content.tag containing id, uuids, words, index, temperature)
non-TEXT frames Passthrough
END / QUIT Deferred until all in-flight variant calls complete
ERROR variant ERROR frame (with content.tag)

Error conditions: None thrown. Per-variant errors produce ERROR frames rather than crashing the pipeline.

createDiversityReducer(options)

Fan-in stream reducer implementing IFrameTransform { source, sink }. Buffers incoming tagged TEXT frames, applies embedding-distance diversity filtering, and emits only the diverse subset when END arrives.

Parameters:

Name Type Required Default Description
options.threshold number No 0.35 Cosine distance threshold (0–1).
options.strategy string No "first" Selection strategy: "first" keeps first-seen (insertion order), "longest" processes longest texts first (sort before greedy filter).
options.embeddingFn (text: string) => vector No internal textToPseudoEmbedding Embedding function. Falls back to the same trigram pseudo-embedding algorithm.
options.maxOutput number No Infinity Maximum diverse items to emit. Clamped to ≥ 1.

Returns: IFrameTransform { source, sink }

Frame protocol:

Input → Output →
TEXT Buffered, not passed through
END / QUIT Flushes buffer: emits diverse subset as TEXT frames then forwards END
non-TEXT Passthrough
empty TEXT frames Filtered (dropped)

Tests

npm test

Runs node --test test/creativity.test.js test/rigor.test.js. Covering: tool creation, DESCRIBE frame emission, tick-driven mode, auto-run with generate-mode=label (pseudo-embedding only), diversity filtering of idea arrays, and empty/single-element edge cases in filterDiverseIdeas.

Requirements

  • Node.js >= 22.0.0
  • ESM only
  • Peer dependencies: @polyweave/core, @polyweave/sampler, @polyweave/self-improving — all must be installed alongside this package.

Caveats

  • When no embedding adapter is injected, the trigram pseudo-embedding fallback is used. This is fast but semantically weak — it only detects near-literal similarity based on 3-character shingles. For meaningful semantic diversity filtering, inject a proper embedding adapter.
  • The llm adapter is used for both generation and ban synthesis. If options.generate is false, no LLM calls are made and variants are labeled with their sampler seed strings. Ban synthesis is also skipped when options.generate is false.
  • All adapters must implement the Polyweave frame duplex protocol ({ source, sink }).
  • The tool is stateful: a second TOOL_CALL arriving while a pipeline is running will overwrite the in-flight state.

Dependencies

Dependencies

ID Version
@polyweave/core *
@polyweave/errors *
@polyweave/sampler *
@polyweave/self-improving *

Development Dependencies

ID Version
@rigor/core *

Keywords

polyweave creativity sampler diversity embedding innovation llm
Details
npm
2026-07-11 15:22:50 +00:00
68
John Dvorak
SEE LICENSE IN LICENSE
latest
12 KiB
Assets (1)
Versions (3) View all
1.0.5 2026-07-11
1.0.2 2026-07-10
1.0.1 2026-07-10